We are going to use Mock Service Worker to fake the API. So, instead directly calling the API or mock the window.fetch it's better to mock the server behaviour.
It gives us a sandbox to play with the API server without polluting the window.fetch and no need to setup test API server for testing purpose.
You have created a beautiful Feed feature on your site and now you want to test that feature.
Your code is look like this
importReactfrom'react';functionFeed(){const[feeds,setFeeds]=React.useState([]);React.useEffect(()=>{constctl=newAbortController();fetch('https://api.domain.com/feeds').then(res=>res.json()).then(res=>setFeeds(res.data)).catch(()=>{// Log the error});returncleanup(){ctl.abort();};},[])return (<ul>{feeds.map(feed=>(<li>{feed.title}</li>))}</ul>);}
Though, it's really simple frature where you want to show users feed to the UI.
Somehow you confused about how to test the feature because you "must" fetch the feeds data from the API, but since this is a test you won't it calls directly to the API.
The Journey
Now you are searching possible solutions on the Internet. Because you use React and of course use Jest and testing-library (it is default setup from CRA), someone suggest you to mock the window.fetch object.
You intersted and put the solution in your test like this
// Import all dependencies... constMOCK_FEEDS=[/** Feed objects here */];beforeAll(()=>{jest.spyOn(window,'fetch');});describe('Feed Feature Test',()=>{it('should shows users feed',()=>{window.fetch.mockResolvedValueOnce({ok:true,json:async ()=>({data:MOCK_FEEDS}),})// Your testing goes here... });});
You test the code and it looks green (meaning your test is pass) and you happy with that.
Tomorrow has come. You forgot about the "empty state" UI for Feed feature and today you made it.
Because the empty state UI need en error HTTP NOT FOUND, you need to refactor the test.
// Import all dependencies... constMOCK_FEEDS=[/** Feed objects here */];constMOCK_FEEDS_NOT_FOUND=[];beforeAll(()=>{jest.spyOn(window,'fetch');});beforeEach(()=>{jest.restoreAllMocks()});describe('Feed Feature Test',()=>{it('should shows empty state UI when feeds not found',()=>{window.fetch.mockResolvedValueOnce({ok:false,status:404,json:async ()=>({data:MOCK_FEEDS_NOT_FOUND}),})// Your testing goes here... });it('should shows users feed',()=>{window.fetch.mockResolvedValueOnce({ok:false,json:async ()=>({data:MOCK_FEEDS}),})// Your testing goes here... });});
Your test is passes, and you happy again.
The next morning, you received calls from project manager. He want you to add a "like (❤️)" button and you do it.
From this task, you have to update the feature to fetch two API endpoints. First you must use the GET /feeds and then POST /feeds/{id}/like to perform "like this feed".
After adding some codes, you refactor the test again, but realise that the mocked fetch doesn't care of the URL. What ever API endpoint you hit, it always returns the same response and potential a bugs on the future.
The test now is look like this
// Import all dependencies... constMOCK_FEEDS=[/** Feed objects here */];constMOCK_FEEDS_NOT_FOUND=[];beforeAll(()=>{jest.spyOn(window,'fetch');});beforeEach(()=>{window.fetch.mockImplementation(async (url,config)=>{switch (url){case'/feeds':return{ok:true,json:async ()=>MOCK_FEEDS,};case'/feeds/1/like':return{ok:true,json:async ()=>({liked:true})};default:thrownewError(`Unhandled request: ${url}`);}});});describe('Feed Feature Test',()=>{it('should shows empty state UI when feeds not found',()=>{// Your testing goes here... });it('should shows users feed',()=>{// Your testing goes here... });it('should able to like the feed',()=>{// Your testing goes here...})});
The tests are passes except the first one. You asking "why?" then realise there is no request handler for GET /feeds with 404 http status.
Luckly, there is a tool called Mock Service Worker. It is a mocking tool for API and live in the network level.
Simply imagine that you can mock your API server and use it inside your test without makes you headache.
And the interesting part of this are:
Support Rest API and GraphQL
Support Node and Browser (meaning you can use it with Express, etc)
And many more...
Enough for explaining, let's install the requirement and refactor the tests.
First, install the msw.
npm i -D msw
To use the MSW, you need to make request handlers with specific HTTP method and URL. So, it gives you an ability to make GET 200 /feeds and GET 404 /feeds. It also support URL parameter like this /feeds/:feedId/like.
Now update the test like this...
// Import all dependencies... import{rest}from'msw';import{setupServer}from'msw/node';constMOCK_FEEDS=[/** Feed objects here */];constMOCK_FEEDS_NOT_FOUND=[];consturl=(path)=>`https://api.domain.com/feeds/${path}`;constdefaultHandlers=[rest.get(url(path),(req,res,ctx)=>{returnres(ctx.json(MOCK_FEEDS));}),rest.get(url(path),()=>{returnres(ctx.status(404),ctx.json(MOCK_FEEDS_NOT_FOUND),);})];constserver=setupServer(...defaultHandlers);describe('Feed Feature Test',()=>{beforeAll(()=>{server.listen();});afterEach(()=>{server.resetHandlers();});afterAll(()=>{server.close();});it('should shows empty state UI when feeds not found',()=>{// Your testing goes here... });it('should shows users feed',()=>{// Your testing goes here... });it('should able to like the feed',()=>{// Adds the "POST /feeds/:feedId/like" request handler as a part of this test.server.use(rest.post(url('feeds/:feedId/like'),(req,rest,ctx)=>{returnres(ctx.json({liked:true}),);}),);// Your testing goes here...})});
Boom! your tests are passes. Not only the tests are passes it also provide better DX because you can easily create powerful URL routing.
You can also split the handlers into another file to prevent writing the handlers repeatedly. Making it reusable across all tests.
Not only provide better DX, MSW also offers you HTTP Cookie and other HTTP features. Awesome!
Conclusion
Mocking (and testing) HTTP request is one of challenging part, but MSW comes with powerful feature, cross-platform and use simple approach to tackle this problem.
MSW provide native-like approach by intercepting the network and returns the handlers we made instead of the real one. Prevent you to pollute the window.fetch by mocking it and potentially caused bugs.
Seamless REST/GraphQL API mocking library for browser and Node.js.
MSW 2.0 is finally here! 🎉 Read the Release notes and please follow the Migration guidelines to upgrade. If you're having any questions while upgrading, please reach out in our Discord server.
We've also recorded the most comprehensive introduction to MSW ever. Learn how to mock APIs like a pro in our official video course:
Mock Service Worker
Mock Service Worker (MSW) is a seamless REST/GraphQL API mocking library for browser and Node.js.
Features
Seamless. A dedicated layer of requests interception at your disposal. Keep your application's code and tests unaware of whether something is mocked or not.
Deviation-free. Request the same production resources and test the actual behavior of your app. Augment an existing API, or design it as you go when there is none.
Familiar & Powerful. Use Express-like routing syntax to intercept requests. Use parameters, wildcards, and regular expressions to match requests…