msw-request-assertions
v1.0.5
Published
Request assertions for MSW
Maintainers
Readme
MSW Request Assertions
A powerful testing library that provides custom assertion matchers for Mock Service Worker (MSW). Test your API interactions with confidence by asserting on request properties like headers, body, query parameters, and more.
Features | Installation | API | Contributing
[!IMPORTANT]
Before using this library, please read MSW's official guidance on avoiding request assertions. The MSW team recommends testing how your application reacts to requests rather than asserting on request details. This library should primarily be used for edge cases, like request details have no observable application behavior.
✨ Features
- ✅ Assert on any aspect of HTTP and GraphQL requests
- ✅ Works with Vitest and Jest
📦 Installation
npm install --save-dev msw-request-assertions msw
# or
yarn add --dev msw-request-assertions msw
# or
pnpm add --save-dev msw-request-assertions msw🚀 Quick Start
Setup for Vitest
// vitest.setup.ts
import 'msw-request-assertions/vitest'// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
},
})// tsconfig.json
{
"compilerOptions": {
// ...rest of your options
"types": ["vitest/globals", "msw-request-assertions/vitest"]
}
}
Setup for Jest
[!NOTE]
Jest support is not a first-class citizen due to complex setup requirements between ESM and CJS modules. While we provide Jest compatibility, our main development and testing efforts are focused on Vitest. Feel free to raise an issue if you encounter Jest-specific problems.
// jest.setup.ts
import 'msw-request-assertions/jest'// jest.config.js
module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}// tsconfig.json
{
"compilerOptions": {
// ...rest of your options
"types": ["vitest/globals", "msw-request-assertions/jest"]
}
}
Basic Example
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
// Create MSW handlers
const userHandler = http.post('/api/users', () => {
return HttpResponse.json({ id: 1, name: 'John Doe' })
})
const server = setupServer(userHandler)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('should create user with correct data', async () => {
const userData = { name: 'John Doe', email: '[email protected]' }
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
// Assert the request was made
expect(userHandler).toHaveBeenRequested()
// Assert the request body
expect(userHandler).toHaveBeenRequestedWithJsonBody(userData)
// Assert multiple properties at once
expect(userHandler).toHaveBeenRequestedWith({
jsonBody: userData,
headers: { 'content-type': 'application/json' }
})
})💻 API
- Basic Request Matchers
- Body Matchers
- Header Matchers
- URL Matchers
- GraphQL Matchers
- Unified Matcher
- Nth Call Matchers
- Types
Basic Request Matchers
toHaveBeenRequested
Assert that a handler was called at least once.
expect(handler).toHaveBeenRequested()
expect(handler).not.toHaveBeenRequested()toHaveBeenRequestedTimes
Assert that a handler was called exactly n times.
expect(handler).toHaveBeenRequestedTimes(3)
expect(handler).not.toHaveBeenRequestedTimes(1)Body Matchers
toHaveBeenRequestedWithBody
Assert on raw request body (string).
expect(handler).toHaveBeenRequestedWithBody('{"name":"John Doe","email":"[email protected]"}')
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithBody(1, 'first call body')toHaveBeenRequestedWithJsonBody
Assert on JSON request body (parsed object).
expect(handler).toHaveBeenRequestedWithJsonBody({
name: 'John Doe',
email: '[email protected]'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithJsonBody(2, { userId: '123' })Header Matchers
toHaveBeenRequestedWithHeaders
Assert on request headers. Headers are case-insensitive.
expect(handler).toHaveBeenRequestedWithHeaders({
'authorization': 'Bearer token123',
'content-type': 'application/json'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithHeaders(1, { 'x-api-key': 'secret' })URL Matchers
toHaveBeenRequestedWithQueryString
Assert on URL query parameters.
[!NOTE]
The input is a string to avoid serialization issues, especially with arrays and custom formats. This gives you full control over the exact query string format.
expect(handler).toHaveBeenRequestedWithQueryString('?page=1&limit=10')
expect(handler).toHaveBeenRequestedWithQueryString('') // No query params
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithQueryString(2, '?filter=active')
// Custom serialization with qs library
import qs from 'qs'
const params = {
filters: ['active', 'verified'],
sort: { field: 'name', order: 'asc' },
page: 1
}
// Different serialization formats
const standardFormat = '?' + new URLSearchParams({
'filters[]': 'active',
'filters[]': 'verified'
}).toString()
const qsFormat = '?' + qs.stringify(params, {
arrayFormat: 'brackets',
encode: false
})
// Results in: ?filters[]=active&filters[]=verified&sort[field]=name&sort[order]=asc&page=1
expect(handler).toHaveBeenRequestedWithQueryString(qsFormat)toHaveBeenRequestedWithHash
Assert on URL hash fragment.
expect(handler).toHaveBeenRequestedWithHash('#section1')
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithHash(1, '#top')toHaveBeenRequestedWithPathParameters
Assert on URL path parameters (for dynamic routes).
// For route: /users/:userId/posts/:postId
expect(handler).toHaveBeenRequestedWithPathParameters({
userId: '123',
postId: '456'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithPathParameters(1, { id: '789' })GraphQL Matchers
toHaveBeenRequestedWithGqlQuery
Assert on GraphQL query string.
expect(gqlHandler).toHaveBeenRequestedWithGqlQuery(`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`)
// Nth call variant
expect(gqlHandler).toHaveBeenNthRequestedWithGqlQuery(1, 'query { users { id } }')toHaveBeenRequestedWithGqlVariables
Assert on GraphQL variables.
expect(gqlHandler).toHaveBeenRequestedWithGqlVariables({
id: '123',
filters: { status: 'active' }
})
// Nth call variant
expect(gqlHandler).toHaveBeenNthRequestedWithGqlVariables(2, { userId: '456' })Unified Matcher
toHaveBeenRequestedWith
Assert on multiple request properties at once. See RequestPayload for all available properties.
// HTTP example
expect(handler).toHaveBeenRequestedWith({
jsonBody: { name: 'John' },
headers: { 'authorization': 'Bearer token' },
queryString: '?page=1',
hash: '#top',
pathParameters: { userId: '123' }
})
// GraphQL example
expect(gqlHandler).toHaveBeenRequestedWith({
gqlQuery: 'query GetUser($id: ID!) { user(id: $id) { name } }',
gqlVariables: { id: '123' }
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWith(2, {
jsonBody: { action: 'update' },
headers: { 'content-type': 'application/json' }
})Nth Call Matchers
All matchers have an "nth" variant to assert on specific call positions. The first argument is the call number (1-indexed).
// Basic matchers
expect(handler).toHaveBeenNthRequestedWithBody(1, 'first call body')
expect(handler).toHaveBeenNthRequestedWithJsonBody(2, { data: 'second call' })
expect(handler).toHaveBeenNthRequestedWithHeaders(3, { 'x-retry': '2' })
// URL matchers
expect(handler).toHaveBeenNthRequestedWithQueryString(1, '?page=1')
expect(handler).toHaveBeenNthRequestedWithHash(2, '#section2')
expect(handler).toHaveBeenNthRequestedWithPathParameters(1, { id: '123' })
// GraphQL matchers
expect(gqlHandler).toHaveBeenNthRequestedWithGqlQuery(1, 'query { users }')
expect(gqlHandler).toHaveBeenNthRequestedWithGqlVariables(2, { limit: 10 })
// Unified matcher
expect(handler).toHaveBeenNthRequestedWith(3, {
jsonBody: { name: 'Jane' },
queryString: '?include=profile'
})Types
RequestPayload
The payload object used with toHaveBeenRequestedWith matcher.
type RequestPayload = {
// HTTP/GraphQL body content
body?: string;
jsonBody?: unknown;
// HTTP headers (case-insensitive)
headers?: Record<string, string>;
// URL components
queryString?: string;
hash?: string;
pathParameters?: Record<string, string>;
// GraphQL specific
gqlQuery?: string;
gqlVariables?: unknown;
}🤝 Contributing
Development
Local Development
pnpm i
pnpm testBuild
pnpm buildRelease
This repo uses Release Please to release.
To release a new version
- Merge your changes into the
mainbranch. - An automated GitHub Action will run, triggering the creation of a Release PR.
- Merge the release PR.
- Wait for the second GitHub Action to run automatically.
- Congratulations, you're all set!
