Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Testing With Vitest in JavaScript

Testing With Vitest in JavaScript

~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

To wrap up the practical chapters, let's write automated tests for our budget app – the same testing library (Vitest) used by the TypeScript tutorial works identically with plain JavaScript.

Installing Vitest

npm install --save-dev vitest
package.json
{
  "name": "haushaltsbuch-app",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "test": "vitest run"
  },
  "devDependencies": {
    "vitest": "^2.1.0"
  }
}

The first test

Vitest follows the usual convention: test files end in .test.js and usually live right next to the file being tested:

src/categories.test.js
import { describe, it, expect } from 'vitest';
import { isValidCategory, DEFAULT_CATEGORIES } from './categories.js';

describe('isValidCategory', () => {
  it('returns true for a known category', () => {
    expect(isValidCategory('Rent')).toBe(true);
  });

  it('returns false for an unknown category', () => {
    expect(isValidCategory('Unknown')).toBe(false);
  });

  it('contains exactly four default categories', () => {
    expect(DEFAULT_CATEGORIES).toHaveLength(4);
  });
});
npm test

describe() groups related tests, it() (also callable as test()) defines a SINGLE test case, expect() formulates the actual assertion.

Important matcher functions

  • toBe(value) – exact equality with ===, suited for primitives.
  • toEqual(object) – DEEP content comparison for objects/arrays (since toBe would almost always fail due to the reference comparison from chapter 12).
  • toBeTruthy() / toBeFalsy() – the truthy/falsy check from chapter 5.
  • toThrow() – checks whether a function throws an error.
  • toHaveLength(n) – checks the .length property of arrays/strings.
import { describe, it, expect } from 'vitest';

function calculateBalance(transactions) {
  return transactions.reduce((sum, t) => sum + t.amount, 0);
}

describe('calculateBalance', () => {
  it('sums several transactions correctly', () => {
    const transactions = [{ amount: 2400 }, { amount: -850 }, { amount: -60 }];
    expect(calculateBalance(transactions)).toBe(1490);
  });

  it('returns 0 for an empty array', () => {
    expect(calculateBalance([])).toBe(0);
  });
});

Testing error handling

import { describe, it, expect } from 'vitest';
import { InvalidTransactionError } from './errors.js';

function validateTransaction(transaction) {
  if (!transaction.description) {
    throw new InvalidTransactionError('Description is missing');
  }
}

describe('validateTransaction', () => {
  it('throws InvalidTransactionError when the description is missing', () => {
    expect(() => validateTransaction({ amount: -18 })).toThrow(InvalidTransactionError);
  });
});

Achtung: expect(() => ...) requires a WRAPPER arrow function around the call when using toThrow(). expect(validateTransaction(...)).toThrow() would run the function IMMEDIATELY and throw the error uncontrolled, instead of letting Vitest catch it in a controlled way.

Testing asynchronous functions

import { describe, it, expect } from 'vitest';
import { loadTransactions } from './fileStorage.js';
import { FileNotFoundError } from './errors.js';

describe('loadTransactions', () => {
  it('throws FileNotFoundError for a missing file', async () => {
    await expect(loadTransactions('nonexistent.json')).rejects.toThrow(FileNotFoundError);
  });
});

The test function itself becomes async, and expect(...) is combined with await.rejects checks that the returned promise gets REJECTED with the expected error, instead of fulfilled.