Introduction
I recently started working on integration tests for a React application with Redux-connected components, specifically Redux-Toolkit. I quickly learned a common tool to use is the Mock Service Worker (MSW) library to mock network requests; this library allows you to intercept outgoing network requests and mock their responses making it super easy to test the implementation details of your components.
Setting up MSW in my application is quick and easy - after installing the library, you set up the server and integrate with your testing environment (Jest, Vitest,..), and you create the handlers to mock your requests and corresponding responses. Sounds easy enough, right?
Well, not so much so if you're using Axios! 🙃
The problem
After setting up MSW and creating the handlers I started running into issues. The requests were being intercepted by MSW but my tests kept failing because the Redux state was not being updated. I knew my Redux store and component were working as expected and the requests were being dispatched just fine, yet my tests kept failing. Was something else misconfigured somewhere?! I could not find any problems anywhere in my codebase. I spent hours debugging and asking around hopelessly until I noticed something interesting... the response data from the successful requests was empty, yet the error response was undefined
. So the data was being lost somewhere between MSW and the Axios request in my Redux slice. Hmmmm.
Issue discovered
This discovery lead me to wonder if these two libraries were even compatible and sure enough, the first Google result shed some light on the issue. MSW and Axios are compatible, however, there are some discrepancies in the way the libraries handle network requests causing the response body to be dropped or unrecognized in the Axios request.
Solution
A good solution that worked for me was to get Axios to be mocked to use native fetch
while testing, as mentioned by someone in the Github thread above. There are several ways to do this and this is a quick simple solution that allows you to keep the benefits of axios in your actual application.
jest.setup.js or jest.polyfills.js (depending on your setup)
import axios from 'axios';
// Globals needed for MSW
const { TextEncoder, TextDecoder, ReadableStream } = require('node:util');
Reflect.set(global, 'TextEncoder', TextEncoder);
Reflect.set(global, 'TextDecoder', TextDecoder);
Reflect.set(global, 'ReadableStream', ReadableStream);
const { Request, Response, Headers, FormData } = require('undici');
Reflect.set(global, 'Request', Request);
Reflect.set(global, 'Response', Response);
Reflect.set(global, 'Headers', Headers);
Reflect.set(global, 'FormData', FormData);
// Create a custom adapter that uses fetch
axios.defaults.adapter = async (config) => {
const { url, method, data, headers } = config;
// Convert axios config to fetch config
const response = await fetch(url, {
method,
body: data ? JSON.stringify(data) : undefined,
headers: {
'Content-Type': 'application/json',
...headers,
},
});
const responseData = await response.json();
return {
data: responseData,
status: response.status,
statusText: response.statusText,
headers: response.headers,
config,
};
};
Example Redux slice
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
export const getMyData = createAsyncThunk(
'example/getMyData',
async (location) => {
const response = await axios.get(`https://google.com/get/mydata`, {
headers: {
'x-api-key': 'YOUR_API_KEY',
},
});
return response.data;
},
);
/* Initial state */
/* Slice fn */
Example MSW Handlers
import { http, HttpResponse } from 'msw';
http.get(`${baseURL}/get/mydata`, ({ request }) => {
console.log('Captured a "GET /get/mydata"', request.method, request.url);
return HttpResponse.json('123ABCDE', { status: 200 });
}),
Start and stop MSW in SetupTests.js
import { server } from './mocks/server';
// Establish API mocking before all tests.
beforeAll(() => {
server.listen();
});
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());
// Clean up after the tests are finished.
afterAll(() => {
server.close();
});
Hope this helps somebody else in their testing endeavors!
Top comments (0)