Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion packages/app/universal-testing-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Reusable testing utilities that are potentially relevant for both backend and fr
- [mockAnyResponse](#msw-mockanresponse)
- [mockValidResponseWithAnyPath](#mockvalidresponsewithanypath)
- [mockValidResponseWithImplementation](#mockvalidresponsewithimplementation)
- [mockSseStream](#mockssestream)
- [mockttp integration with API contracts](#mockttp-integration-with-api-contracts)
- [Basic usage](#basic-usage-1)
- [Query params support](#query-params-support)
Expand Down Expand Up @@ -184,17 +185,83 @@ mswHelper.mockValidResponseWithAnyPath(dualModeContractWithPathParams, server, {

### mockValidResponseWithImplementation

Custom handler for complex logic. REST contracts only.
Custom handler for complex logic. The `handleRequest` callback receives the full MSW request info and returns the response body. Works with REST and dual-mode contracts.

```ts
// REST contract
mswHelper.mockValidResponseWithImplementation(postContractWithPathParams, server, {
pathParams: { userId: ':userId' },
handleRequest: async (requestInfo) => ({
id: `id-${requestInfo.params.userId}`,
}),
})

// Dual-mode contract — handleRequest for JSON, events for SSE
mswHelper.mockValidResponseWithImplementation(dualModeContract, server, {
handleRequest: async (requestInfo) => {
const body = await requestInfo.request.json()
return { id: `impl-${body.name}` }
},
events: [{ event: 'completed', data: { totalCount: 1 } }],
})
```

#### Per-call status codes with `MswHelper.response()`

By default, all calls return the same status code (`params.responseCode` or `200`). To vary the status code per call, wrap the return value with `MswHelper.response(body, { status })`:

```ts
let callCount = 0
mswHelper.mockValidResponseWithImplementation(contract, server, {
handleRequest: () => {
callCount++
if (callCount === 1) {
return MswHelper.response({ error: 'Server error' }, { status: 500 })
}
return { id: 'success' } // plain return still works
},
})
```

This is fully non-breaking — returning the body directly (without `MswHelper.response()`) continues to work as before.

Status code priority: `MswHelper.response({ status })` > `params.responseCode` > `200`.

### mockSseStream

Returns an `SseEventController` that lets you emit SSE events on demand during tests, instead of returning all events immediately. Works with SSE and dual-mode contracts.

```ts
// SSE contract — emit events on demand
const controller = mswHelper.mockSseStream(sseContract, server)

const response = await fetch('/events/stream')

controller.emit({ event: 'item.updated', data: { items: [{ id: '1' }] } })
controller.emit({ event: 'completed', data: { totalCount: 1 } })
controller.close()

// With path params
const controller = mswHelper.mockSseStream(sseContractWithPathParams, server, {
pathParams: { userId: '42' },
})

// Dual-mode contract — SSE side streams on demand, JSON side uses responseBody
const controller = mswHelper.mockSseStream(dualModeContract, server, {
responseBody: { id: '1' },
})

// JSON requests get immediate response
const jsonRes = await fetch('/events/dual', { headers: { accept: 'application/json' } })

// SSE requests get streaming response
const sseRes = await fetch('/events/dual', { headers: { accept: 'text/event-stream' } })
controller.emit({ event: 'completed', data: { totalCount: 42 } })
controller.close()
```

The controller is fully type-safe — event names and data shapes are inferred from the contract's `serverSentEventSchemas`.

## mockttp integration with API contracts

`MockttpHelper` provides the same unified `mockValidResponse` API. The contract type determines params:
Expand Down
Loading
Loading