DEV Community

Janeth Betancourt
Janeth Betancourt

Posted on

Integration testing of Redux-components with MSW

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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 });
  }),
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

Hope this helps somebody else in their testing endeavors!

Top comments (0)