MSW, The Request-ed Hero
01/03/2022
dev
In tests I’ve written in the past, I attempted to mock out things like axios or fetch to ship back data that resembled what I expected. I know, I know. Not a good idea.
This created so much random hassle. Missing small bits of the mocked API could cause errors that would be hard to follow and find. This was especially prevalent when I was first learning TDD.
So there has to be a better way.. right.. RIGHT!?
Right.
In swoops MSW, the network request interceptor that everyone needs.
The plain and simple idea here is to hijack the requests coming from your application…
// This is the hijacked line, wherever it may live in your code.
await fetch('hello');
and return data you specify that you know to be correct in a handler that looks like this:
rest.get("hello", (req, res, ctx) => {
return res(ctx.status(200), ctx.json({ hello: "world" });
})
You get access to the request & response objects so you can access all the things you may want.
You can return different status codes depending on some data you POST-ed with.
There are so … many … options.
I’ll pop a reasonably complete configuration near the bottom. However, from here you just need to start the server listening for your tests and it’ll hook into all the API requests you’ve set up handlers for.
The general consensus looks like this to get it hooked up:
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
I did this in Vitest, but it’s the same with Jest too. That simple. Then you just need to assert against the data, no mocking of packages that you don’t need to. Focus guaranteed.
So yeah, that’s a little run through. There’s still a lot more out there, so definitely check out their docs. You can catch GraphQL requests, mocking out errors is a cinch, you can even run it in the browser.
Ahhhh, real cool!
Here’s the promised, semi-full example (this is for Node & unit testing primarily):
// server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// handlers.ts
import { rest } from 'msw';
export const handlers = [
rest.get('hello', (req, res, ctx) => {
return res(ctx.status(200), ctx.json({ hello: 'world' }));
})
];
// hello-world.spec.ts
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { server } from '../__mocks__/server';
import { useHelloStore } from '@/stores/helloStore';
describe('Hello World Testaroo', () => {
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
it('Returns valid Hello World data from the endpoint', async () => {
const helloStore = useHelloStore();
await helloStore.doTheAsync();
expect(helloStore.hello).toEqual('world');
});
});
Your implementation and use may vary. But that should give you a general feel.
Have fun with MSW. It wants to be your friend. Maybe it should be your BFF.