React test if something hasn't happened after useEffect

Viewed 2961

I'm using react-testing-library and jest to test if my API is not invoked when a certain prop is set. Currently the test succeeds immediately without finishing the useEffect() call. How do I make the test wait until useEffect has finished, so I can be certain the API has not been called?

The code:

const MyComponent = ({ dontCallApi }) => {
  React.useEffect(() => {
    const asyncFunction = async () => {
      if (dontCallApi) {
        return
      }

      await callApi()
    }
    asyncFunction
  }, [])

  return <h1>Hi!</h1>
}

it('should not call api when dontCallApi is set', async () => {
  const apiSpy = jest.spyOn(api, 'callApi')
  render(<MyComponent dontCallApi />)
  expect(apiSpy).not.toHaveBeenCalled()
})
1 Answers

In your case, you could spy on React.useEffect and provide an alternative implementation. jest.spyOn(React, "useEffect").mockImplementation((f) => f()) so now you dont't have to care about the handling of useEffect anymore.

If you also want to test useEffect in a descent way you may extract the logic in a custom hook and use the testing library for hooks with the renderHooks function to test your use case.

I would test your Component like this:

import React from "react";
import { MyComponent } from "./Example";
import { render } from "@testing-library/react";
import { mocked } from "ts-jest/utils";
jest.mock("./api", () => ({
  callApi: jest.fn(),
}));
import api from "./api";
const mockApi = mocked(api);

jest.spyOn(React, "useEffect").mockImplementation((f) => f());
describe("MyComponet", () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  it("should not call api when dontCallApi is set", async () => {
    render(<MyComponent dontCallApi />);
    expect(mockApi.callApi).toHaveBeenCalledTimes(0);
  });

  it("should call api when is not set", async () => {
    render(<MyComponent />);
    expect(mockApi.callApi).toHaveBeenCalledTimes(1);
  });
});

Edit 03.07.2020

I recently found out that there is a possibility to query something like you wanted without mocking useEffect. You could simply use the async utilities of react testing library and get the following:

import React from "react";

import { MyComponent } from "./TestComponent";
import { render, waitFor } from "@testing-library/react";
import { api } from "./api";

const callApiSpy = jest.spyOn(api, "callApi");

beforeEach(() => {
  callApiSpy.mockImplementation(() => Promise.resolve());
});
afterEach(() => {
  callApiSpy.mockClear();
});
describe("MyComponet", () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  it("should not call api when dontCallApi is set", async () => {
    render(<MyComponent dontCallApi />);
    await waitFor(() => expect(callApiSpy).toHaveBeenCalledTimes(0));
  });

  it("should call api when is not set", async () => {
    render(<MyComponent />);
    await waitFor(() => expect(callApiSpy).toHaveBeenCalledTimes(1));
  });
});

to get more information about this take a look at the async utilities docs

Related