Integration testing of Redux-components with MSW
Tags: react, testing, webdev, javascript
Published: 2024-06-14 18:26:46

## 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](https://mswjs.io/) 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! :upside_down_face:

## 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](https://github.com/mswjs/msw/issues/2026) 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!