Skip to content

Instantly share code, notes, and snippets.

@isqua
Last active May 28, 2025 08:10
Show Gist options
  • Save isqua/ff58187d02376aebd74385ad1c2c55fd to your computer and use it in GitHub Desktop.
Save isqua/ff58187d02376aebd74385ad1c2c55fd to your computer and use it in GitHub Desktop.

How We Built a Scalable Mocking Architecture with MSW and TypeScript

This gist contains examples of how I work with the mocks REST API in tests and storybook in the react application. The examples were written for an article on my blog.

import { delay, http, HttpResponse, type JsonBodyType } from 'msw'
export type ResponseOptions = {
status?: number
delay?: number
once?: boolean
}
export abstract class BaseMock {
protected baseApiUrl: string = '/api'
static createGetMock<T extends JsonBodyType>(
url: string,
response: T,
options: ResponseOptions = {}
) {
const { status = 200, delay: timeout, once } = options
return http.get(url, async () => {
if (timeout) await delay(timeout)
return HttpResponse.json(response, { status })
}, { once })
}
static createPostMock<T extends JsonBodyType>(
url: string,
response: T,
options: ResponseOptions = {}
) {
const { status = 201, delay: timeout, once } = options
return http.post(url, async () => {
if (timeout) await delay(timeout)
return HttpResponse.json(response, { status })
}, { once })
}
static createPatchMock<T extends JsonBodyType>(
url: string,
response: T,
options: ResponseOptions = {}
) {
const { status = 200, delay: timeout, once } = options
return http.patch(url, async () => {
if (timeout) await delay(timeout)
return HttpResponse.json(response, { status })
}, { once })
}
}
import type { RequestHandler } from 'msw';
declare module '@storybook/react' {
declare interface Parameters {
msw?: {
handlers: RequestHandler[];
};
}
}
import { type TUser } from '@/entities/user'
import { BaseMock, type ResponseOptions } from './base-mock'
export class UserMock extends BaseMock {
// Mock for getting all users
getAllUsers(users: TUser[], options: ResponseOptions) {
const url = `${this.baseApiUrl}/users`
return BaseMock.createGetMock(url, users, options)
}
// Mock for getting a user by ID
getUserById(userId: number, user: TUser, options: ResponseOptions) {
const url = `${this.baseApiUrl}/users/${userId}`
return BaseMock.createGetMock(url, user, options)
}
// Mock for creating a user
createUser(user: TUser, options: ResponseOptions) {
const url = `${this.baseApiUrl}/users`
return BaseMock.createPostMock(url, user, options)
}
// Mock for updating a user
updateUser(userId: number, user: TUser, options: ResponseOptions) {
const url = `${this.baseApiUrl}/users/${userId}`
return BaseMock.createPatchMock(url, user, options)
}
}
import { UserMock } from '@/tests/mocks'
const mock = new UserMock()
export const UserListPage = {
parameters: {
msw: {
handlers: [
mock.getAllUsers([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
])
]
}
}
}
import { expect, it } from 'vitest'
import { setupServer } from 'msw/node'
import { render, screen } from '@testing-library/react'
import { UserMock } from '@/tests/mocks'
import { UserListPage } from './UserListPage'
const server = setupServer()
const mock = new UserMock()
it('should render a list of users', async () => {
server.use(
mock.getAllUsers([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
])
)
render(<UserListPage />)
expect(await screen.findByText('Alice')).toBeInTheDocument()
expect(screen.getByText('Bob')).toBeInTheDocument()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment