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);
  });
});
3
  • 1
    Post the relevant code here. Commented Jan 9, 2023 at 11:59
  • Just as an update, I've tried moving from Vitest to Jest and the exact same configuration works correctly in Jest. Commented Jan 11, 2023 at 8:24
  • Hello, have you resolved this issue? I am also facing the same issue with msw. Any solution for this? Commented Feb 9, 2024 at 2:45

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

1 Comment

Already using 1.1.0.
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

1 Comment

Now it says Request is not a constructor
1

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

1 Comment

I guess with @testing-library/react you can use findBy methods like: await screen.findByRole('button', { name: 'buttons.search' });

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.