3

I am attempting to test a component that uses the useAsync hook using the @testing-library/react .

If I use jest.mock on the TestAPI module, followed by getTest.mockResolvedValueOnce(testArray); on the getTest function then I would expect the mock to correctly return the test values.

Test :

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import TestPanel from "./TestPanel";
import { getTest } from "./TestAPI";

jest.mock("./TestAPI");

it("renders cards correctly", async () => {
  const testArray = ["hi there"];
  getTest.mockResolvedValueOnce(testArray);

  render(<TestPanel />);

  expect(getTest).toHaveBeenCalledTimes(1);
  expect(getTest).toHaveBeenCalledWith();

  await waitFor(() => expect(screen.getByText("hi there")).toBeInTheDocument());
});

Component to be tested :

import React from "react";
import { useAsync } from "react-async-hook";
import { getTest } from "./TestAPI";

export default function TestPanel() {
  const { result: elems } = useAsync(getTest, []);

  return (
    <div>
      {elems &&
        elems.map((elem) => {
          return <div>{elem}</div>;
        })}
    </div>
  );
}

API call :

import React from "react";
import axios from "axios";

export async function getTest(): Promise {
  const response = await axios.get("/someservice");

  return response.data || [];
}

However if I run the test then an exception is thrown saying that the "elems.map" is undefined.

On closer inspection it seems that the elems is a promise.

enter image description here

Is there something that I am doing wrong?

Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225
  • 1
    Instead of mocking the hook, why don't you just test it using [react-hooks-testing-library](https://github.com/testing-library/react-hooks-testing-library)? You can render the hook `const {result: hookResult, waitFor} = renderHook(() => useAsync(getTest, []));` and then wait for the result to be non-null: `await waitFor(() => hookResult.result);` and then write expect cases: `expect(getTest).toHaveBeenCalledTimes(1);` – Kapobajza Sep 06 '21 at 12:28

2 Answers2

1

These are your issues: react-async-hook & jest

Here react-async-hook uses instanceof Promise but Jest.mockResolvedValueOnce returns not a real JS Promise, but a promise-like object. So the useAsync() treats the mock as a sycn function.

The solution is to use mockImplementationOnce instead of mockResolvedValueOnce

getTest.mockImplementation(async () => testArray)

From the docs:

mockFn.mockResolvedValueOnce(value)

Syntactic sugar function for:

jest.fn().mockImplementationOnce(() => Promise.resolve(value));

But, sadly, it's not just a syntactic sugar.

x00
  • 13,643
  • 3
  • 16
  • 40
  • 1
    ok thanks . replacing that line with your line seems to make it work for me now. Still not exactly sure why, it still hurts my brain a bit. Will read up on the links you posted. thanks :) – Oliver Watkins Sep 11 '21 at 11:45
0

i think you should mock your return from the useAsync hook itself.

jest.mock("path/to/useAsync", () => ({
  result: ["test"]
}));`
HenriDev
  • 587
  • 7
  • 13
  • I tried this : jest.mock("react-async-hook/useAsync", () => ({result: ["test"],})); . Is that what you meant? It doesnt seem to work – Oliver Watkins Sep 07 '21 at 14:18
  • yes that's idea but try [this](https://stackoverflow.com/a/60282832/12219726) and make sure to mock on top – HenriDev Sep 07 '21 at 17:08