RTK Query Testing Error with MSW and vitest
Asked Answered
B

4

6

I'm trying to test a component in React that uses RTK Query + MSW + Vitest. It works fine on the browser but while testing RTK Query with vitest it throws this error:

{ status: 'FETCH_ERROR', error: 'TypeError: fetch failed' }

I've setup a public repo with the configuration i'm using to replicate the error.

Another error RTK Query throws only while testing is:

TypeError: Failed to parse URL from /api/test?page=1&limit=10

This happens when i setup the fetchBaseQuery - baseUrl as empty or as a relative URL. I have to specify 'http://localhost...' or it wont work. But, it works in the browser.

Repo with the error

Followed some articles on the matter with no success Searched Stackoverflow and found Searched RTK query repo issues and found a similar issue but with jest instead of vitest I've tried clearing query cache between tests - no success.

App.jsx

import { useTestQuery } from "./services/test";

export const App = () => {
  const { data } = useTestQuery({ page: 1, limit: 10 });
  console.log(data);
  return (
    <>
      <h1>{data ? data.data.map((i) => i) : "loading"}</h1>
    </>
  );
};

redux/index.js

import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { testApi } from "../services/test";

const store = configureStore({
  reducer: {
    [testApi.reducerPath]: testApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat([testApi.middleware]),
});
setupListeners(store.dispatch);

export default store;

services/apiSetup.js

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const emptyApi = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl:"http://localhost:3000"
  }),
  tagTypes: ["test"],
  endpoints: () => ({}),
});

services/test.js

import { emptyApi } from "./apiSetup";

export const testApi = emptyApi.injectEndpoints({
  endpoints: (build) => ({
    test: build.query({
      query: ({ page = 1, limit = 10 }) => {
        return { url: `test?page=${page}&limit=${limit}` };
      },
      providesTags: ["test"],
      transformErrorResponse: (result) => console.log(result),
    }),
  }),
  overrideExisting: false,
});

export const { useTestQuery,usePrefetch } =
  testApi;

mocks/server.js

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

mocks/handler.js

import { test } from "./handlers/test";

export const handlers = [...test];

mocks/handlers/test.js

import { rest } from "msw";

function randomIntFromInterval(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

export const test = [
  rest.get("http://localhost:3000/test", (req, res, ctx) => {
    console.log(req)
    return res(
      ctx.delay(randomIntFromInterval(100, 1000)),
      ctx.status(200),
      ctx.json({ data: [1, 2, 3, 4, 5], total: 5 })
    );
  }),
];

testSetup.js

import { expect, afterEach, vi } from "vitest";
import { server } from "../mocks/server";
import { cleanup } from "@testing-library/react";
import matchers from "@testing-library/jest-dom";

// extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers);


beforeAll(() => server.listen({ onUnhandledRequest: "bypass", cors: true }));

afterEach(() => {
  server.resetHandlers();
  cleanup();
});

afterAll(() => server.close());

App.test.jsx

import React from "react";
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import store from "../../redux";
import App from "./";

const wrapper = (children) => {
  return <Provider store={store}>{children}</Provider>;
};

function setup(jsx) {
  return {
    ...render(wrapper(jsx)),
  };
}

describe("App test", () => {
  test("Should test", async () => {
    setup(<App />);
    await screen.findByText(/12345/);
    screen.debug(undefined, 50000);
  });
});
Brei answered 9/1, 2023 at 11:5 Comment(3)
Post the relevant code here.Folio
Just as an update, I've tried moving from Vitest to Jest and the exact same configuration works correctly in Jest.Brei
Hello, have you resolved this issue? I am also facing the same issue with msw. Any solution for this?Sundried
D
2

If you're here like me and

  1. Providing global fetch doesn't work
  2. Updating MSW doesn't work
  3. You're defining full baseURL and mocking that correctly

But it still doesn't work, check if you have tried fixing jest polyfills before as part of the MSW migration guide.

https://mswjs.io/docs/migrations/1.x-to-2.x/#frequent-issues

They suggest polyfilling Request/Response which messes with your fetch in RTK, which was needed in my case but I added it because instructions said so.

Just remove this section and see if still works:

const { Blob, File } = require('node:buffer')
const { fetch, Headers, FormData, Request, Response } = require('undici')
 
Object.defineProperties(globalThis, {
  fetch: { value: fetch, writable: true },
  Blob: { value: Blob },
  File: { value: File },
  Headers: { value: Headers },
  FormData: { value: FormData },
  Request: { value: Request }, 
  Response: { value: Response }, <-- This one caused issues with RTK for me
})

Hope it helps.

Dopester answered 18/3 at 12:36 Comment(0)
C
1

Try to install msw 1.1.0, after release https://github.com/mswjs/msw/pull/1545 everything seems to work fine with your setup.

Cub answered 17/3, 2023 at 10:3 Comment(1)
Already using 1.1.0.Villarreal
M
1

If you get FETCH_ERROR in your tests with RTK Query, MSW, vitest, then most likely you'll need to define fetch in a setup function, similar to: https://github.com/reduxjs/redux-toolkit/blob/67a69e876812282ad7fe5590d173a88bd7b80282/packages/toolkit/jest.setup.js

//@ts-ignore
const nodeFetch = require('node-fetch')
//@ts-ignore
global.fetch = nodeFetch
//@ts-ignore
global.Request = nodeFetch.Request
Marpet answered 16/4, 2023 at 23:58 Comment(1)
Now it says Request is not a constructorVillarreal
M
0

To everyone troubled by this issue: If you're using msw v2, the solution mentioned above has already been fixed in this version. However, it is important to note that Vitest does not wait for RTK to asynchronously return a response, resulting in the returned value being undefined. Therefore, you need to add waitFor in RTL's expect like this:

await waitFor(() => {
  expect(button).toHaveTextContent('10');
});

It took me 4 hours to finally find the solution!!

ref: https://mcmap.net/q/1126744/-how-to-test-rtk-query-with-react-testing-library

Muscadine answered 27/6 at 7:35 Comment(1)
I guess with @testing-library/react you can use findBy methods like: await screen.findByRole('button', { name: 'buttons.search' });Mindoro

© 2022 - 2024 — McMap. All rights reserved.