I’ll explain the Pokemon in a minute. But first, we all agree unit testing is not the most fun! Particularly testing API requests. However, I recently discovered Mock Service Worker, a simple and efficient way of mocking axios
and fetch
in testing. Simple because of the way it integrates into applications, and efficient because it operates at a network level, making it function synonymously with an actual mocking server.
Taken from the npm documentation, here are some of the ways MSW behaves differently than other mocking libraries:
- Intercepts requests on the network level, not the application level.
- If your think of your application as a box, Mock Service Worker lives in its own box next to yours, instead of opening and altering it for the purpose of mocking.
- Agnostic of request-issuing libraries, so you can use it with
fetch
,axios
,react-query
, you-name-it. - The same mock definition can be reused for unit, integration, E2E testing, and debugging.
MSW in action
Okay, let’s show an example of an axios.get
request mocked with Mock Service Worker. For this, I‘ll fetch data from the (very professional) REST API, PokeAPI!
If you’d like to follow along, go ahead and add the msw
module in your package.json
file as a dev dependency with npm i msw --save-dev
.
The fetchData()
function above makes an axios.get
request to PokeAPI and returns the name
property of a Pokemon in the response (this example returns the name ‘venusaur’). Simple enough. Now let’s look at how this is mocked in our test:
The first few things to import to the test file are {rest} from ‘msw’
and {setupServer} from ‘msw/node’
. We then create a server
variable and set it equal to the setupServer
function. Essentially what setupServer
is doing is:
- setting up a server — as you probably guessed — that listens for any requests made to the provided API,
- intercepts them, and
- handles how it should respond.
This is how MSW functions at the network level. The second thing to pass to setupServer
is a callback function that takes the req, res, ctx
arguments. ctx
is the context used to help build out the responses we want back. For instance, the status of the response can be set with ctx.status(200)
.
In this case, we return a json
object with ctx.json({ name: 'pikachu' })
.
From here the mocking server still needs some additional configuration with a few Jest callbacks. On line 15, beforeAll(() => server.listen())
tells the server to start listening. afterAll(() => server.close())
tells the server to stop listening once the tests are complete.
Now we are ready to write our test! We call await fetchData()
and store the value in pokemon
. The assertion is for (pokemon).toEqual('pikachu')
. The real PokeAPI server returns the name ‘venusaur’
. However, Mock Service Worker intercepts the request and returns ‘pikachu’
in the response, therefore the test passes!
Tip: The ctx.json()
object in the mocked response needs to match the format of what’s returned by fetchData()
. Since fetchData
returns res.data.name
, the mocked response also needs to return a name
property.