Using MSW for mocking API requests
Writing unit tests is a great way to add confidence to your code and figuring out all the possible cases and edge cases during development. It saves a lot of hours and it make sure that if someone is doing a refactor in code he don't break any existing functionality.
In smallcase we write unit tests for every component that we create or any function that we use in our application. We use React testing library to write unit tests. This is a great library for writing unit tests as it mocks the exact behavior of the browser while rendering the component.
So while writing unit tests for components on one good day I ran into a dead lock situation. I was developing a feature which was urgent and APIs were not ready yet. So there were two possible ways for me to develop the frontend side of the feature.
- Develop the UI first using storybook
- Develop the data flow later
So developing the UI was a piece of cake and was ready in couple of days. But the APIs were still not ready and developing the data flow and logic was kind a stuck due to this. So I decided to not use API while developing the flow. But instead use unit tests to mock the API and write logic according to that. This approach worked like a charm and saved me a lot of time and added confidence to my code.
For mocking this api request we used MSW. And now it has become a common practice for me to write test cases for the components who uses api call and develop the logic and data flow accordingly.
Image from Unsplash
So now the question which many of you people will have is why MSW when you can simply mock axios or fetch and use that. And its a valid
question, why to use MSW when you can mock the API request handler which will also solve this problem. I agree to this but there are some problems
with it.
So there are some problems while mocking axios or other API request handler. Lets discuss them here
So let me give you some common problems which arises while mocking requests using API handler (fetch or axios used commonly).
So the first problem is that we are actually not making any API requests at all in our code during testing.
The API handler is being mocked and the API calls are never happening it is just returning us a promise.
The other problem is, since it is not an API call so we can’t be very sure that the actual server will accept our API call with current headers and body and will send us the response. One example of this will be, say you are making an API call to a server which is a post request and it accepts a field in a body as
requestIdand you mock the API handler for a response but what you can’t do here is to add a validation check that only respond when the requestId is present in the body or before responding check the auth token in headers. So there is a chance that someone might come and change the body params or the expected key or structure and the test will still pass. So this now adds a bug to our code. Obviously, there are workarounds to this but they still are limited to an extent.Lastly, mocking the API handler in tests adds a huge dependency on API handler in the codebase, Say if we decided to move away from the API handler we are using at some point and start using other API handler for making API calls then our tests will break because we were mocking some API handler in tests and now we are using other options to make API calls. So our tests will break.
So these are some problems which arrives while using the mocked API handler in tests and you might some day change something and you test may pass and in production you will end up breaking your code.
To read more about why you shouldn't mock you API handler for mocking API calls you can read this article by Kent C. Dodds on why not to mock fetch for making api calls in tests
Benefits of MSW
So there are some benefits of using MSW for mocking API calls. And the reason why you should be using it
- So there are some benefits of MSW over normal Axios mocking, One is definitely the actual API request is being made and it is then intercepted and replied by MSW server, so we can add validation check for body or params and headers easily and we can reject the calls if something is missing from params or body. Resulting in test failure and alerting the developer about the issue.
The MSW can also be used in the browser during development when the API is broken, APIs are down or API is not ready. MSW can help with mocking APIs in the browser as well which can help during development.
It removes the dependency from Axios in our code to an extent, so if we start using some other packages to make API call in our package we are sure that since we are not mocking Axios our test will not break due to this. And if they are breaking then it means we haven't integrated the new api handler correctly
Some additional benefits of MSW are
Mock Service Worker supports both
restAPIS andgraphqlAPIS.Mock Service Worker can mock
serverandservice workersin your components and tests which makes it more beneficial for development of your application which actually needing the api or writing tests peacefully.
Usage of MSW
So while writing the data flow for the feature, I first wrote the test cases for the data flow and then wrote the functions for using the data flow because this was the only way by which I could have checked that whether my code is working fine or not during development.
Take a look at this test which is mocking api call for me.
it('should return error status in case of an error from server', async () => {
server.use(
rest.post(
`${API_URL}${apiMap.HOLDINGS_AUTHORIZATION_STATUS}`,
(req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
status: 'success',
message: 'Internal Server Error',
}),
);
},
),
);
const result = await getHoldingsAuthStatusUrl({});
expect(result).toEqual({ Success: '0' });
});
So here this test is mocking the api call and the response is an error. So this test is helping me to check whether my function is returning correct response in case of error or not.
Now take a look at this function which is actually making this api call and returning the status to us.
function getHoldingsAuthStatusUrl(queryStringObject) {
try {
const { data } = await pollHandler(
makeHoldingsAuthStatusApiCall, // function to make api call with parameters
handleAuthStatusValidation, // handle validation of api response and break polling if expected response is received
4, // number of times to poll api
250, // time interval in milliseconds between each poll
);
const finalRes = {
...queryStringObject,
Success: data.data.authorized ? '1' : '0',
};
return finalRes;
} catch (err) {
captureException(err, {
level: Severity.ERROR,
});
return {
...queryStringObject,
Success: '0',
};
}
}
So using msw is as simple as eating a cookie. You can integrate msw in your testing setup and start using it straight away.
As shown in the above test you can import the server from either msw/node or from the place where it is located in your code as
an extraction.
Then in your test you can pass the handlers to the server so he can respond to the api call based on the handler you have provided.
In my example i wanted to mock a specific api endpoint so i added the code like this
server.use(
`${API_ENDPOINT}`, // The first argument is the endpoint which needs to be mocked
(req, res, ctx) => { // second argument is the callback function which will be used as a handler to the endpoint
return res(
ctx.status(200),
ctx.json({
status: 'success',
message: 'Success',
}),
);
},
)
The seconds argument is the handler which will be used to respond to the api call. So here you will get the 3 arguments
- req - request object
- res - response function
- ctx - context object
You can add your validation check and data manipulation logic in the handler just like your original server and then return the response. The possibilities are endless here.
By this you make sure that your function will always send the correct params or body along with headers to the server and then it can expect a response and do its handling for the response. And if someone changes the code and change the structure or key of the request accidentally then our test will fail and he will be alerted about the issue.
Note: MSW server has a koa inspired api so you can read about koa.js if you are interested, to see the syntax and how koa server works
Future Goals
Since we are using msw for mocking api calls in tests. Our plans are to use it to full extent in our codebase.
Use it to mock service worker of browsers so we can develop the app in the browser even when the API is down or not ready.
We want to use it in storybook as they already have a plugin for msw, so we can make our stories more interactive and anyone from the team can check the entire UI handling and flow from storybook for a component.