Vitest Cheatsheet
API Types & Mocks (Kubb, OpenAPI)
Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
What is Kubb?
Kubb is a code generator that takes an OpenAPI specification and produces typed TypeScript clients, Zod schemas, React Query hooks, and MSW mock factories — eliminating the need to write boilerplate type definitions or mock data by hand.
Installation
npm install -D @kubb/cli @kubb/core @kubb/plugin-oas @kubb/plugin-ts @kubb/plugin-zod @kubb/plugin-client @kubb/plugin-msw
Kubb v3 (2024) renamed every
@kubb/swagger-*package to@kubb/plugin-*(@kubb/swagger→@kubb/plugin-oas). If you see@kubb/swagger-tsetc. in a config, it's a pre-v3 setup.
kubb.config.ts
import { defineConfig } from '@kubb/core' import { pluginOas } from '@kubb/plugin-oas' import { pluginTs } from '@kubb/plugin-ts' import { pluginZod } from '@kubb/plugin-zod' import { pluginClient } from '@kubb/plugin-client' import { pluginMsw } from '@kubb/plugin-msw' export default defineConfig({ root: '.', input: { path: './openapi.yaml', // or a URL: 'https://api.example.com/openapi.json' }, output: { path: './src/gen', clean: true, }, plugins: [ pluginOas({ validate: true }), // Generate TypeScript types pluginTs({ output: { path: 'types' } }), // Generate Zod schemas for runtime validation pluginZod({ output: { path: 'zod' } }), // Generate fetch/axios client functions pluginClient({ output: { path: 'clients' }, baseURL: 'https://api.example.com', }), // Generate MSW handlers from the spec pluginMsw({ output: { path: 'mocks' } }), ], })
Running the Generator
npx kubb # or add to package.json: # "generate:api": "kubb"
Generated Output Structure
src/gen/
types/
User.ts # TypeScript interfaces
GetUsers200.ts
zod/
userSchema.ts # Zod schemas
clients/
getUsers.ts # fetch wrappers
createUser.ts
mocks/
getUsersMock.ts # MSW handlers
createUserMock.tsUsing Generated Types
// src/gen/types/User.ts (generated) export type User = { id: number name: string email: string role: 'admin' | 'viewer' } export type GetUsersResponse = User[]
// your code import type { User } from '../gen/types/User' function renderUser(user: User) { return `${user.name} <${user.email}>` }
Using Generated Zod Schemas
// src/gen/zod/userSchema.ts (generated) import { z } from 'zod' export const userSchema = z.object({ id: z.number(), name: z.string(), email: z.string().email(), role: z.enum(['admin', 'viewer']), }) export type User = z.infer<typeof userSchema>
// runtime validation in tests import { userSchema } from '../gen/zod/userSchema' it('validates API response shape', async () => { const raw = await fetch('/api/users/1').then(r => r.json()) const user = userSchema.parse(raw) // throws ZodError if invalid expect(user.email).toContain('@') })
Using Generated MSW Handlers
// src/gen/mocks/getUsersMock.ts (generated) import { http, HttpResponse } from 'msw' import type { GetUsersResponse } from '../types/User' export function getUsersMock(data?: Partial<GetUsersResponse>): HttpResponse { return HttpResponse.json([ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' }, ...(data ?? []), ]) } export const getUsersHandler = http.get('/api/users', () => getUsersMock())
// src/mocks/handlers.ts import { getUsersHandler, createUserHandler } from '../gen/mocks' export const handlers = [getUsersHandler, createUserHandler]
Overriding Generated Mocks in Tests
import { server } from '../mocks/server' import { http, HttpResponse } from 'msw' import { getUsersMock } from '../gen/mocks/getUsersMock' it('shows empty state', async () => { server.use( http.get('/api/users', () => HttpResponse.json([])) ) // render and assert empty state... }) it('shows user list', async () => { server.use( http.get('/api/users', () => getUsersMock([ { id: 99, name: 'Bob', email: 'bob@test.com', role: 'viewer' }, ])) ) // render and assert user row... })
Using Generated Client Functions
// src/gen/clients/getUsers.ts (generated) import type { GetUsersResponse } from '../types/User' export async function getUsers(): Promise<GetUsersResponse> { const res = await fetch('/api/users') if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }
// test using the generated client + MSW import { getUsers } from '../gen/clients/getUsers' it('fetches users', async () => { const users = await getUsers() expect(users).toHaveLength(1) // MSW handler returns 1 user expect(users[0].name).toBe('Alice') })
Faker-Based Mock Data (with @kubb/plugin-faker)
npm install -D @kubb/plugin-faker @faker-js/faker
// kubb.config.ts import { pluginFaker } from '@kubb/plugin-faker' plugins: [ pluginFaker({ output: { path: 'faker' } }), ]
Generated:
// src/gen/faker/createUser.ts import { faker } from '@faker-js/faker' import type { User } from '../types/User' export function createUser(override: Partial<User> = {}): User { return { id: faker.number.int(), name: faker.person.fullName(), email: faker.internet.email(), role: faker.helpers.arrayElement(['admin', 'viewer']), ...override, } }
import { createUser } from '../gen/faker/createUser' it('works with seeded faker data', () => { const user = createUser({ role: 'admin' }) expect(user.role).toBe('admin') expect(user.email).toContain('@') })
openapi-ts Alternative
openapi-typescript is a simpler codegen for types only:
npm install -D openapi-typescript npx openapi-typescript ./openapi.yaml -o ./src/gen/api.ts
// src/gen/api.ts (generated) export interface paths { '/users/{id}': { get: { parameters: { path: { id: number } } responses: { 200: { content: { 'application/json': components['schemas']['User'] } } } } } } export interface components { schemas: { User: { id: number; name: string; email: string } } }
// Type-safe fetch using openapi-fetch import createClient from 'openapi-fetch' import type { paths } from './gen/api' const client = createClient<paths>({ baseUrl: 'https://api.example.com' }) const { data, error } = await client.GET('/users/{id}', { params: { path: { id: 1 } }, }) // data is typed as User
Testing Type-Safe Clients with MSW
import { server } from '../mocks/server' import { http, HttpResponse } from 'msw' import { client } from '../api/client' // openapi-fetch client beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) it('GET /users/:id returns typed user', async () => { server.use( http.get('https://api.example.com/users/1', () => HttpResponse.json({ id: 1, name: 'Alice', email: 'a@a.com' }) ) ) const { data } = await client.GET('/users/{id}', { params: { path: { id: 1 } }, }) expect(data?.name).toBe('Alice') })
Key Differences: Kubb vs openapi-typescript
| Kubb | openapi-typescript | |
|---|---|---|
| Types | Yes | Yes |
| Zod schemas | Yes (plugin) | Via openapi-zod-client |
| Client functions | Yes (fetch/axios) | Via openapi-fetch |
| MSW handlers | Yes (plugin) | Manual |
| Faker factories | Yes (plugin) | Manual |
| React Query hooks | Yes (plugin) | Manual |
| Config complexity | Higher | Low |
| Best for | Full code gen pipeline | Types + lightweight client |