Request assertions for MSW
npm install msw-request-assertionsA 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.
!NPM !GitHub CI  
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.
- ✅ Assert on any aspect of HTTP and GraphQL requests
- ✅ Works with Vitest and Jest
``bash`
npm install --save-dev msw-request-assertions mswor
yarn add --dev msw-request-assertions mswor
pnpm add --save-dev msw-request-assertions msw
`typescript`
// vitest.setup.ts
import 'msw-request-assertions/vitest'
`typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
},
})
`
`json
// tsconfig.json
{
"compilerOptions": {
// ...rest of your options
"types": ["vitest/globals", "msw-request-assertions/vitest"]
}
}
`
> [!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.
`typescript`
// jest.setup.ts
import 'msw-request-assertions/jest'
`javascript`
// jest.config.js
module.exports = {
setupFilesAfterEnv: ['
}
`json
// tsconfig.json
{
"compilerOptions": {
// ...rest of your options
"types": ["vitest/globals", "msw-request-assertions/jest"]
}
}
`
`typescript
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: 'john@example.com' }
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' }
})
})
`
- Basic Request Matchers
- toHaveBeenRequested
- toHaveBeenRequestedTimes
- Body Matchers
- toHaveBeenRequestedWithBody
- toHaveBeenRequestedWithJsonBody
- Header Matchers
- toHaveBeenRequestedWithHeaders
- URL Matchers
- toHaveBeenRequestedWithQueryString
- toHaveBeenRequestedWithHash
- toHaveBeenRequestedWithPathParameters
- GraphQL Matchers
- toHaveBeenRequestedWithGqlQuery
- toHaveBeenRequestedWithGqlVariables
- Unified Matcher
- toHaveBeenRequestedWith
- Nth Call Matchers
- Types
- RequestPayload
#### toHaveBeenRequested
Assert that a handler was called at least once.
`typescript`
expect(handler).toHaveBeenRequested()
expect(handler).not.toHaveBeenRequested()
#### toHaveBeenRequestedTimes
Assert that a handler was called exactly n times.
`typescript`
expect(handler).toHaveBeenRequestedTimes(3)
expect(handler).not.toHaveBeenRequestedTimes(1)
#### toHaveBeenRequestedWithBody
Assert on raw request body (string).
`typescript
expect(handler).toHaveBeenRequestedWithBody('{"name":"John Doe","email":"john@example.com"}')
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithBody(1, 'first call body')
`
#### toHaveBeenRequestedWithJsonBody
Assert on JSON request body (parsed object).
`typescript
expect(handler).toHaveBeenRequestedWithJsonBody({
name: 'John Doe',
email: 'john@example.com'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithJsonBody(2, { userId: '123' })
`
#### toHaveBeenRequestedWithHeaders
Assert on request headers. Headers are case-insensitive.
`typescript
expect(handler).toHaveBeenRequestedWithHeaders({
'authorization': 'Bearer token123',
'content-type': 'application/json'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithHeaders(1, { 'x-api-key': 'secret' })
`
#### 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.
`typescript
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.
`typescript
expect(handler).toHaveBeenRequestedWithHash('#section1')
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithHash(1, '#top')
`
#### toHaveBeenRequestedWithPathParameters
Assert on URL path parameters (for dynamic routes).
`typescript
// For route: /users/:userId/posts/:postId
expect(handler).toHaveBeenRequestedWithPathParameters({
userId: '123',
postId: '456'
})
// Nth call variant
expect(handler).toHaveBeenNthRequestedWithPathParameters(1, { id: '789' })
`
#### toHaveBeenRequestedWithGqlQuery
Assert on GraphQL query string.
`typescript
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.
`typescript
expect(gqlHandler).toHaveBeenRequestedWithGqlVariables({
id: '123',
filters: { status: 'active' }
})
// Nth call variant
expect(gqlHandler).toHaveBeenNthRequestedWithGqlVariables(2, { userId: '456' })
`
#### toHaveBeenRequestedWith
Assert on multiple request properties at once. See RequestPayload for all available properties.
`typescript
// 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' }
})
`
All matchers have an "nth" variant to assert on specific call positions. The first argument is the call number (1-indexed).
`typescript
// 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'
})
`
#### RequestPayload
The payload object used with toHaveBeenRequestedWith matcher.
`typescript`
type RequestPayload = {
// HTTP/GraphQL body content
body?: string;
jsonBody?: unknown;
// HTTP headers (case-insensitive)
headers?: Record
// URL components
queryString?: string;
hash?: string;
pathParameters?: Record
// GraphQL specific
gqlQuery?: string;
gqlVariables?: unknown;
}
#### Local Development
`bash`
pnpm i
pnpm test
#### Build
`bash`
pnpm build
This repo uses Release Please to release.
#### To release a new version
1. Merge your changes into the main` branch.
2. An automated GitHub Action will run, triggering the creation of a Release PR.
3. Merge the release PR.
4. Wait for the second GitHub Action to run automatically.
5. Congratulations, you're all set!