Using Vitest, MSW, and Faker in TypeScript
Three tools that changed how I write tests: Vitest for speed, MSW for API mocking, and Faker for realistic data. How to wire them together in TypeScript.
17 May 2024

Three tools that changed how I write tests: Vitest for speed, MSW for API mocking, and Faker for realistic test data. Together, they eliminate the two biggest testing pain points: slow feedback loops and brittle mock data.
Vitest setup
If your project uses Vite, Vitest plugs in directly. It reuses your Vite config, so aliases, plugins, and transforms just work.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
},
});
The globals: true option lets you skip importing describe, it, and expect in every file. Less boilerplate, same behavior.
MSW: mocking at the network level
MSW (Mock Service Worker) intercepts HTTP requests at the network layer. Your code makes real fetch calls. MSW catches them before they leave the process and returns your mock response.
This is fundamentally different from mocking fetch directly. With MSW, your application code runs exactly as it does in production. You are not stubbing internals. You are controlling the boundary.
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
]);
}),
];
export const server = setupServer(...handlers);
Wire it up in your test setup file:
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Now every test in your suite gets consistent API responses without touching vi.mock or vi.fn.
Override handlers per test
The real power of MSW is per-test overrides. You define happy-path handlers globally, then override for specific error scenarios:
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';
it('shows an error when the API fails', async () => {
server.use(
http.get('/api/users', () => {
return new HttpResponse(null, { status: 500 });
})
);
render(<UserList />);
expect(await screen.findByText('Something went wrong')).toBeInTheDocument();
});
The override only lasts for that test. resetHandlers() in afterEach restores the defaults.
Faker: realistic test data
Hardcoded test data is fragile and boring. Faker generates realistic data that exposes edge cases you would never think to write manually.
import { faker } from '@faker-js/faker';
function createMockUser() {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
createdAt: faker.date.past().toISOString(),
};
}
Use it in your MSW handlers:
http.get('/api/users', () => {
const users = Array.from({ length: 10 }, createMockUser);
return HttpResponse.json(users);
});
Every test run gets different data. If your component breaks on a long name or a special character in an email, Faker will find it eventually.
The trade-off
This stack adds dependencies and setup cost. MSW requires understanding its handler model. Faker adds randomness, which can make failures harder to reproduce (fix: seed the random generator with faker.seed(123) for deterministic runs).
But the payoff is significant. Tests run fast, mock real network behavior, and surface bugs that static test data never would.
VS Code tip
Install the Vitest VS Code extension. It shows test results inline, lets you run individual tests with a click, and displays the Vitest UI in a panel. Faster feedback than switching to a terminal.