0

I have a component that uses a hook that fetches data from the server, and I have mocked that hook to return my testing data. Now if the mutate function (returned by the hook) is called, the normal implementation fetches the data again and causes a re-render (I'm using swr, here the mutate reference).

How to I trigger a re-render / setState on a mocked hook?

What I want to test: simply, if the user creates an item the item list should be re-fetched and displayed.

Code to illustrate the issue:

const existing = [...]
const newlyCreated = [...];

useData.mockReturnValue({ data: [existing] });

const { getByRole, findByText } = render(<MyComponent />);
const form = getByRole("form");
const createButton = within(form).getByText("Create");

useData.mockReturnValue({ data: [existing, newlyCreated] });

// createButton.click();
// Somehow trigger re-render???

for (const { name } of [existing, newlyCreated]) await findByText(name);
Elias
  • 3,592
  • 2
  • 19
  • 42
  • Have you considered mocking the API response rather than mocking the `useData` hook? – juliomalves Jul 12 '21 at 10:48
  • @juliomalves yes I have. But having a dedicated hook for fetching data and mocking that was recommended on SO and in issues of `swr`. I initially tried to mock `swr` which didn't really work out. I could mock `fetch` but I don't think that's the way to go, is it? Especially because that's implementation detail. – Elias Jul 12 '21 at 10:58
  • I meant mocking the API responses with requests interceptors like [`nock`](https://github.com/nock/nock) or [`msw`](https://github.com/mswjs/msw). – juliomalves Jul 12 '21 at 11:04
  • @juliomalves Is this preferred over mocking a hook? I would like to read some resources before changing all my code and ending up noticing something isn't how it should be . I quickly looked at the readme of the linked libraries and is it correct that `msw` wouldn't work in my case? As the testing is not performed in an actual browser environment, but rather in js-dom (`msw` use service workers). – Elias Jul 12 '21 at 11:32
  • I just noticed that `msw` has a section about execution in node, so that shouldn't be a problem. – Elias Jul 12 '21 at 11:35

1 Answers1

0

You don't need to trigger a re-render in your test.

The issue is: the button on your UI mutates the data, but since you're mocking useData that mutation isn't happening.

You can simply add mutate() to your mock and assign it a mock function.

You don't need to unit test the inner working of SWR's own mutate() - that's already covered by their own project.

const existing = ["Mercury", "Venus", "Earth", "Mars"];
const newItem = "Jupiter";

test("Create button should call mutate() with correct value", async () => {
  const mutate = jest.fn();
  jest.spyOn(useData, "default").mockImplementation(() => ({
    data: existing,
    mutate
  }));
  let result;
  act(() => {
    result = render(<Form />);
  });
  await waitFor(() => {
    existing.forEach((item) => {
      expect(result.getByText(item)).toBeInTheDocument();
    });
  });
  const input = result.container.querySelector("input");
  fireEvent.change(input, { target: { value: newItem } });
  const createButton = result.getByText("Create");
  createButton.click();
  expect(mutate).toBeCalledWith([...existing, newItem], false);
});

Example on CodeSandbox

Ro Milton
  • 2,281
  • 14
  • 9
  • Indeed I don't need to test that `mutate` works, but I want to test that my screen renders the correct, new data. I suppose I could test if mute was called, and add a second test that covers the case after the mutation. – Elias Jul 29 '21 at 11:18
  • Given to the async nature of my `mutate` call, the test will be executed before the function is called. This is (as I now remember) the primary reason I wanted to mutate the state, so I could await change on the screen. – Elias Jul 30 '21 at 06:24