Error responses
Loading "Error Responses (π solution)"
Run locally for transcripts
I start with restructuring our project a little bit. In order for me to use runtime handlers, I need to have access to the same
server
object created in vitest.setup.ts
. However, importing from the setup file is not recommended.π¦ Although Vitest will cache imports and not evaluate the global hooks twice if you import from the setup file, it caches imports per test file. This works nice in the isolated mode, which Vitest uses by default, but may cause problems if you switch to parallel test runs.
/
/mocks
handlers.ts # Request handlers for happy paths.
node.ts # Node.js integration point.
get-auth-token.test.ts # import { server } from '...'
vitest.setup.ts # import { server } from '...'
With these changes, I can reuse the same
server
object from ./mocks/node.ts
in both vitest.setup.ts
and in the test suite as well.import { server } from './mocks/node.js'
beforeAll(() => {
server.listen({
onUnhandledRequest: 'error',
})
})
afterEach(() => {
server.resetHandlers()
})
afterAll(() => {
server.close()
})
import { http } from 'msw'
import { server } from './mocks/node.js'
import { getAuthToken } from './get-auth-token.js'
At last, I can prepend request handlers using the
server.use()
API to model network scenarios on a per-test basis! The workflow for overriding the network will be the same no matter where I'm going to use it:- Go to the test case that needs a different network behavior;
- Call
server.use()
with a request handler for the same request but describe a different mocked response; - Execute the tested code (the code that does the request).
Here's how the invalid credentials scenario will look like with the runtuime handler in place:
test('throws an error if the user credentials are invalid', async () => {
server.use(
http.post('https://api.example.com/auth', () => {
return new Response(null, { status: 401 })
}),
)
await expect(() =>
getAuthToken({
email: 'kody@epicweb.dev',
password: 'supersecret123',
}),
).rejects.toThrow('Authentication failed: invalid credentials')
})
server.use()
prepends request handlers so they take higher priority. The prepended handlers will persist untilserver.resetHandlers()
function is called, which in our case is called in theafterEach()
hook invitest.setup.ts
.
In a similar fashion, I will create a runtime request handler to emulate a generic 500 server error response in another test case:
test('throws an error if the server responds with an error', async () => {
server.use(
http.post('https://api.example.com/auth', () => {
return new Response(null, { status: 500 })
}),
)
Lastly, I will run the tests to make sure everything works as expected:
β get-auth-token.test.ts (3)
β returns the authentication token on successful authentication
β throws an error if the user credentials are invalid
β throws an error if the server responds with an error
Further reading
Runtime request handlers is a powerful way to describe dynamic network behaviors. I highly recommend you to read the π Network behavior overrides best practice to learn more how you can utilize it during developing and testing your projects.