When doing Test-Driven Development with React, writing tests can be more difficult once the APIs and the HTTP calls are required.
When I first tried doing TDD, I tried to test everything using jest.mock()
or jest.spyOn()
. Most of the times, Jest was enough to write tests but the test code became long and messy due to a lot of stubbing with mockResolvedValue()
for every single API needed across multiple components.
It can be noticed that half of the test code is about setting up the required stub response to run my test successfully. Would it help if I can reuse some of the stub responses across different tests? One can argue reusing and having stubs in a different place may make it harder to follow when tests go wrong. However, it can be imagined from seeing the code below that the test code will be flooded with stubs.
Let us take a look at another test case.
Here the return value of axios.get()
is stubbed. This could be because we don’t want to make a network call for unit tests and we want to have control over what we test. However, with this approach there are some concerns. First of all, the test cases and the stub responses have to worry about the response schema
of axios
or equivalent HTTP client. Somehow, I must know that axios
returns the actual response under data
property. It also means that there is no check on if I am using the HTTP client properly (until I start the app and check on the browser manually).
MSW
This led me to msw
(Mock Service Worker).
msw creates a mock server which intercepts all network requests and return the handlers (including stub responses) defined by you.
This will give you a few benefits:
- Default handler can be defined and specific handlers can be used to override in specific tests.
- No need to make assumptions on the response schema of the HTTP client.
- Tests will fail if HTTP client is not being called appropriately.
Let us look at an example using React + Typescript.
Step 1: Install
$ npm install --save-dev msw
Step 2: Define handlers
Step 3: Setup Server
Step 4: Setup Tests
Step 5: Write Tests
We will look at the same two tests above to see how they are changed.
For the first test case, it can be noticed that the response that was stubbed with mockResolvedValue
is now replaced with mswServer.use()
.
This is because the test overrides the default handler defined as handlers
in handlers.ts
. If the default handler can be used, mswServer.use()
can be removed. This reduced the test code and also makes it possible for other component’s reuse the handlers whether they use default handlers or override handlers.
Let us look at the next test case. Again, I overrode the handler response.
Not much difference compared to the first test case. Interesting fact is that when I output the result of axios.get()
, it prints like something this. It is nice that it gives details of the axios response schema.
Bonus
Sometimes you might want to run a fake API server which provides the required endpoints for development.
Step 1: Setup Worker
Step 2: Initialize Mock Service Worker and Run
Here, the start
command used for npm run start
was modified to include a command to initialize msw
. This will ensure that msw
is initialized every time npm run start
is executed.
Conclusion
There is no right or wrong approach. Making a sample using msw
was a fresh experience for me because it allowed me to reuse responses, not having to worry about response schema, and extra safety on the usage of HTTP client.
Most of the times, I write tests to cover more than user’s behaviors such as if a specific method was called or if a specific method was called with correct parameters (London TDD). It is easier to write tests by creating test doubles and stubs and msw
could overlap each other.
Therefore, I would like to try msw
in a future project for testing the network layer or for a fake Backend API server for development.
Full source can be found at:
Reference: