0

I am practicing because I am interested in the React Testing Library with Jest.

I'm writing a unit test of the component, but I keep getting errors, so I'd like to ask you a question.

First, I am writing a component for webtoon information and there is a custom hook called useWebtoonsQuery in it.

The useWebtoonsQuery is as follows.

const useWebtoonsQuery = () => {
  const webtoons = new WebtoonService();
  const queryClient = useQueryClient();

  const toggleFollowing = useMutation(({ id }: IToggle) => webtoons.followWebtoon(id));

  const webtoonAlimToggle = (id: number) => {
    const queryClient = useQueryClient();
    return useMutation(() => webtoons.updateWebtoonAlim(id));
  };

  return {
    toggleFollowing,
    webtoonAlimToggle,
  };
};

export default useWebtoonsQuery;

And I wrote the test code as follows to confirm that the bell-shaped button is pressed well in the unit test (if pressed well, the color of the icon will change).

function withRouter(routes: React.ReactNode, initialEntry = "/") {
  return (
    <MemoryRouter initialEntries={[initialEntry]}>
      <Routes>{routes}</Routes>
    </MemoryRouter>
  );
}

function withQuery(ui: React.ReactNode) {
  const testClient = createTestQueryClient();
  const { rerender, ...result } = render(
    <QueryClientProvider client={testClient}>{ui}</QueryClientProvider>
  );

  return {
    ...result,
    rerender: (rerenderUi: React.ReactElement) =>
      rerender(
        <QueryClientProvider client={testClient}>
          {rerenderUi}
        </QueryClientProvider>
      ),
  };
}


// WebtoonInfo.test.tsx
jest.mock("../../../hooks/useWebtoonsQuery", () => ({
  __esModule: true,
  default: () => ({
    data: testData,
    toggleFollowing: {
      mutate: jest.fn(),
    },
    webtoonAlimToggle: jest.fn().mockReturnValue({
      mutate: jest.fn(),
    }),
  }),
}));

describe("WebtoonInfo component", () => {
 
  it("should call webtoonAlimToggle.mutate when bell button is clicked", () => {
    withQuery(
      withRouter(
        <Route
          path="/"
          element={
            <RecoilRoot>
              <WebtoonInfo data={testData} />
            </RecoilRoot>
          }
        />
      )
    );

    const bellButton = screen.getByLabelText("bell");
    userEvent.click(bellButton);

    const useWebtoonsQuery = jest.fn().mockReturnValue({
      webtoonAlimToggle: jest.fn().mockReturnValue({
        mutate: jest.fn(),
      }),
    });
    const { webtoonAlimToggle } = useWebtoonsQuery();

    expect(webtoonAlimToggle.mutate).toHaveBeenCalledTimes(1);
  });
});

However, contrary to the expected results, the following error occurred.

expect(received).toHaveBeenCalledTimes(expected)

Matcher error: received value must be a mock or spy function

Received has value: undefined

  145 |     const { webtoonAlimToggle } = useWebtoonsQuery();
  146 |
> 147 |     expect(webtoonAlimToggle.mutate).toHaveBeenCalledTimes(1);
      |                                      ^
  148 |   });
  149 | });
  150 |

I want to mock custom hook for useQuery and proceed with UI testing only.

And we are going to test the custom hook separately.

However, I am not writing the code well because I am not knowledgeable, so I would appreciate it if you could tell me how to solve it.

김정수
  • 611
  • 4
  • 9
  • 22

1 Answers1

0

you're trying to access the mutate function of webtoonAlimToggle from the mocked useWebtoonsQuery hook directly. However, the way you've mocked the useWebtoonsQuery hook in your test is not properly returning the desired values. Try it this way,

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { QueryClientProvider } from "react-query";
import { RecoilRoot } from "recoil";
import WebtoonInfo from "./WebtoonInfo";
import useWebtoonsQuery from "../../../hooks/useWebtoonsQuery";

jest.mock("../../../hooks/useWebtoonsQuery");

describe("WebtoonInfo component", () => {
  it("should call webtoonAlimToggle.mutate when bell button is clicked", () => {
    const mockMutate = jest.fn();

    useWebtoonsQuery.mockReturnValue({
      webtoonAlimToggle: jest.fn().mockReturnValue({
        mutate: mockMutate,
      }),
    });

    render(
      <MemoryRouter initialEntries={["/"]}>
        <Routes>
          <Route
            path="/"
            element={
              <RecoilRoot>
                <WebtoonInfo data={testData} />
              </RecoilRoot>
            }
          />
        </Routes>
      </MemoryRouter>
    );

    const bellButton = screen.getByLabelText("bell");
    userEvent.click(bellButton);

    expect(mockMutate).toHaveBeenCalledTimes(1);
  });
});

Here the jest.mock function is being used to mock the useWebtoonsQuery hook. Then, inside the test case, useWebtoonsQuery.mockReturnValue specifying the desired return value of the mocked hook.

This ensures that the webtoonAlimToggle function returns an object with a mutate function that could be spied on and assert against.

Nazrul Chowdhury
  • 1,483
  • 1
  • 5
  • 11
  • Thank you for the detailed explanation. However, as a result of executing the code above, the mocked part is not recognized, and the following error occurs. ` TypeError: Cannot destructure property 'webtoonAlimToggle' of '(0 , _useWebtoonsQuery.default)(...)' as it is undefined.` – 김정수 Jul 04 '23 at 04:41
  • that seems strange because i don't see webtoonAlimToggle is being destructured anywehre in the updated code! try deleting the cache folder and running the test again. – Nazrul Chowdhury Jul 04 '23 at 09:50