Testing with TypeScript
Testing with TypeScript
~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
To wrap up the practical tools: automated tests for our library management system, with Vitest – the same test runner we already met in "React for Professionals" chapter 44, here with no framework binding.
Installing Vitest
npm install --save-dev vitest{
"scripts": {
"start": "tsx src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest"
}
}Vitest understands TypeScript NATIVELY, with no extra configuration – NO separate compilation step before testing needed, EXACTLY like tsx from chapter 2 for normal execution.
Testing the error classes from chapter 25
import { describe, it, expect } from 'vitest';
import { MediumNotFoundError, MediumNotAvailableError } from './LibraryError.js';
describe('MediumNotFoundError', () => {
it('stores the ISBN and a meaningful message', () => {
const error = new MediumNotFoundError('978-0-618-64015-7');
expect(error.isbn).toBe('978-0-618-64015-7');
expect(error.message).toContain('978-0-618-64015-7');
expect(error).toBeInstanceOf(Error); // inheritance from chapter 25 works as expected
});
});Testing Repository<T> generically
import { describe, it, expect, beforeEach } from 'vitest';
import { Repository } from './Repository.js';
import { HasIsbn } from '../models/HasIsbn.js';
interface TestItem extends HasIsbn {
name: string;
}
describe('Repository<T>', () => {
let repository: Repository<TestItem>;
beforeEach(() => {
repository = new Repository<TestItem>();
});
it('starts empty', () => {
expect(repository.count()).toBe(0);
});
it('adds items', () => {
repository.add({ isbn: '123', name: 'Test' });
expect(repository.count()).toBe(1);
});
it('finds by ISBN', () => {
repository.add({ isbn: '123', name: 'Test' });
const found = repository.findByIsbn('123');
expect(found?.name).toBe('Test');
});
it('returns undefined for an unknown ISBN', () => {
expect(repository.findByIsbn('unknown')).toBeUndefined();
});
});TestItem extends HasIsbn defines a MINIMAL test type satisfying the constraint from chapter 18, instead of having to create a full Book object just for the test – a common testing pattern: as LITTLE as possible, as MUCH as needed.
Testing asynchronous code (chapter 26)
import { describe, it, expect } from 'vitest';
import { AsyncBookRepository } from './AsyncBookRepository.js';
import { MediumNotFoundError } from '../errors/LibraryError.js';
describe('AsyncBookRepository', () => {
it('throws MediumNotFoundError for an unknown ISBN', async () => {
const repository = new AsyncBookRepository();
await expect(repository.findByIsbn('unknown'))
.rejects.toThrow(MediumNotFoundError);
});
});it('...', async () => {{...}}) – the test function itself is async, EXACTLY as learned in chapter 26. await expect(...).rejects.toThrow(...) is Vitest's dedicated pattern for checking that a PROMISE gets rejected with a SPECIFIC error type.
Bonus: type checking as part of CI
{
"scripts": {
"start": "tsx src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"verify": "npm run typecheck && npm run test"
}
}npm run verify combines chapter 2's typecheck with the actual tests in ONE command – a common "before commit/deploy" check covering BOTH error classes: TYPE errors (which tsc finds) AND LOGIC errors (which tests find). vitest run instead of just vitest exits after ONE pass, instead of staying in watch mode – important for CI environments.
Tipp: Rule of thumb: type checking AND tests are COMPLEMENTARY, not redundant – TypeScript catches "wrong type passed", tests catch "correct type, but wrong BEHAVIOR" (e.g. an off-by-one calculation). Both together give considerably more confidence than either tool alone.