Casuso
Casuso

Reputation: 89

RTK Query Testing Error with MSW and vitest

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);
  });
});

Upvotes: 6

Views: 3502

Answers (4)

GeorgeCheng
GeorgeCheng

Reputation: 11

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://stackoverflow.com/a/72493058/18743126

Upvotes: 0

Alexander Troshchenko
Alexander Troshchenko

Reputation: 330

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.

Upvotes: 2

naXa stands with Ukraine
naXa stands with Ukraine

Reputation: 37993

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

Upvotes: 1

Chiril
Chiril

Reputation: 77

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

Upvotes: 1

Related Questions