darx
darx

Reputation: 1657

Mocking React custom hook with Jest

I need to mock my custom hook when unit testing React component. I have read few tutorials and stackoverflow answers to this simple task, but without luck to implement it correctly.

My simplest possible setup for single test is as following:

// TestComponent.js

import React from "react";
import useTest from "./useTest";

const TestComponent = () => {
  const { state } = useTest("initial_value");

  return <div>{state}</div>;
};

export default TestComponent;
// useTest.jsx - simple custom hook

import React, { useState } from "react";

const useTest = (initialState) => {
  const [state] = useState(initialState);
  return { state };
};

export default useTest;

// TestComponent.test.jsx - my test case

import React from "react";
import { render } from "@testing-library/react";
import TestComponent from "./TestComponent";

jest.mock("./useTest", () => ({
  useTest: () => "mocked_value",
}));

test("rendertest", () => {
  const component = render(<TestComponent />);
  expect(component.container).toHaveTextContent("mocked_value");
});

So I trying to mock useTest custom hook to return "mocked_value", instead of "initial_value" from real custom hook. But above code just gives me this error:

TypeError: (0 , _useTest.default) is not a function

      3 | 
      4 | const TestComponent = () => {
    > 5 |   const { state } = useTest("initial_value");
        |                     ^
      6 | 
      7 |   return <div>{state}</div>;
      8 | };

I have also tried:

import useTest from './useTest';
// ...
jest.spyOn(useTest, "useTest").mockImplementation(() => "mocked_value");
import useTest from './useTest';
// ...
jest.spyOn(useTest, "useTest").mockReturnValue("mocked_value");

But both gives me error Cannot spy the useTest property because it is not a function; undefined given instead.

How do I implement this test?

Upvotes: 26

Views: 42717

Answers (3)

Rafael Silva
Rafael Silva

Reputation: 97

For those who use the right hook it can be done this way

import * as useFetchMock from "../../../../hooks/useFetch";

it("should show loading component on button when state is loading", () => {
    jest.spyOn(useFetchMock, "default").mockReturnValue({
        result: null,
        message: null,
        status: "pending",
        loading: true,
        fetch: jest.fn(),
    });

    render(<Component />);

    expect.assertions(1);

    expect(screen.getByTestId(testIdComponentB)).toBeInTheDocument();
});

import your custom hook directly and use 'default' to interact directly

Upvotes: 4

darx
darx

Reputation: 1657

I'm answering to myself. This way it's working:

jest.mock("./useTest", () => ({
  useTest: () => ({ state: 'mocked_value' }),
}));

And if I want to use default export in custom hook:

jest.mock("./useTest", () => ({
  __esModule: true,
  default: () => ({ state: 'mocked_value' }),
}));

Also, if I want to also use setState method in my hook and export it, I can mock it like this:

const mockedSetState = jest.fn();

jest.mock("./useTest", () => ({
  useTest: () => ({ state, setState: mockedSetState }),
}));

And now it's possible to check if setState has been called once:

expect(mockedSetState).toHaveBeenCalledTimes(1);

Upvotes: 55

Kelvin Nguyen
Kelvin Nguyen

Reputation: 81

Since you use export default useTest in useTest module, it expects to get that function reference from a default attribute in your mock.

Try this:

jest.mock("./useTest", () => ({
  default: () => "mocked_value",
}));

If you want to avoid confusion, you could try export const useTest = ... in useTest module and then import { useTest } from './useTest' in your component. No need to change your test if using this approach.

Upvotes: 2

Related Questions