How to mock react custom hook returned value?
Asked Answered
P

4

124

Here is my custom hook:

  export function useClientRect() {
    const [scrollH, setScrollH] = useState(0);
    const [clientH, setClientH] = useState(0);
    const ref = useCallback(node => {
      if (node !== null) {
        setScrollH(node.scrollHeight);
        setClientH(node.clientHeight);
      }
    }, []);
    return [scrollH, clientH, ref];
  }
}

I want each time that it is called, it return my values. like:

jest.mock('useClientRect', () => [300, 200, () => {}]);

How can I achieve this?

Plemmons answered 17/2, 2020 at 20:34 Comment(4)
What have you tried so far? Did you take a look at the Jest docs? jestjs.io/docs/en/manual-mocks#mocking-user-modulesDownstroke
@Timo looked... no luck. I think I may need to first spy on useClientRect then mock the return.Plemmons
Have you tried github.com/testing-library/react-hooks-testing-library?Mccrea
what if you mock scrollHeight/clientHeight instead of mocking hook? https://mcmap.net/q/182128/-mocking-clientheight-and-scrollheight-in-react-enzyme-for-testWiskind
P
227

Load the hook as a module. Then mock the module:

jest.mock('module_path/module_name', () => ({
    useClientRect: () => [300, 200, jest.fn()]
}));

mock should be called on top of the file outside test fn. Therefore we are going to have only one array as the mocked value.

If you want to mock the hook with different values in different tests:

import * as hooks from 'module_path/module_name';

it('a test', () => {
    jest.spyOn(hooks, 'useClientRect').mockImplementation(() => ([100, 200, jest.fn()]));
    //rest of the test
});
Plemmons answered 18/2, 2020 at 14:11 Comment(11)
mother of god, pointing out that it needed to be at the top was helpful - somehow no one has mentioned this in the dozen other tutorials i've looked atEteocles
For me it gives the error that "useClientRect is not a function undefined given instead"Espouse
@MuhammadHaseeb You need to replace useClientRect with the name of your hook. In this example the hook returns an array that needs to be mocked.Plemmons
@Homa yeah I put my hook name but my hook returns and objectEspouse
@muhammad-haseeb you need to replace the array with a mocked object. In your code console.log the value that your hook is returning and replace the array like: jest.spyOn(hooks, 'useYourHookName').mockImplementation(() => ({a: 100, b: 200, c: jest.fn()}));.Plemmons
What should be the module_name?Catton
@Mohammad Kermani when you have a hook file, it cab be something like '../folder_name/hook_file_name.ts'. when you mock a hook from a npm library it will be the name of the npm library.Plemmons
In my particular case, I had to do import hooks from 'module_name instead of import * as hooks from 'module_name'. With the latter, the top-level mock was called, but the spy was not.Foreknow
full file code would be much helpful.Juggernaut
I facing issues when the custom hook is default exported. How do I spy on it's returned values in different scenarios?Juggernaut
Does this work if you are passing an argument with the hook?Micra
P
63

Adding on to this answer for typescript users encountering the TS2339: Property 'mockReturnValue' does not exist on type error message. There is now a jest.mocked helper you can call to mock with Type defs (which is a port of the ts-jest/utils mocked function).

import useClientRect from './path/to/useClientRect';

jest.mock('./path/to/useClientRect');

const mockUseClientRect = jest.mocked(useClientRect);

describe("useClientRect", () => {
  it("mocks the hook's return value", () => {
    mockUseClientRect.mockReturnValue([300, 200, () => {}]);
    // ... do stuff
  });

  it("mocks the hook's implementation", () => {
    mockUseClientRect.mockImplementation(() => [300, 200, () => {}]);
    // ... do stuff
  });
});
Prolocutor answered 29/3, 2022 at 14:36 Comment(1)
especially for Typescript usage this provides the best answer! thanksLor
B
17

Well, this is quite tricky and sometimes developers get confused by the library but once you get used to it, it becomes a piece of cake. I faced a similar issue a few hours back and I'm sharing my solution for you to derive your solution easily.

My custom Hook:

  import { useEffect, useState } from "react";
  import { getFileData } from "../../API/gistsAPIs";
    
  export const useFilesData = (fileUrl: string) => {
    const [fileData, setFileData] = useState<string>("");
    const [loading, setLoading] = useState<boolean>(false);
    useEffect(() => {
      setLoading(true);
      getFileData(fileUrl).then((fileContent) => {
        setFileData(fileContent);
        setLoading(false);
      });
    }, [fileUrl]);
    
    return { fileData, loading };
  };

My mock code: Please include this mock in the test file outside of your test function. Note: Be careful about the return object of mock, it should match with the expected response.

const mockResponse = {
  fileData: "This is a mocked file",
  loading: false,
};
jest.mock("../fileView", () => {
  return {
    useFilesData: () => {
      return {
        fileData: "This is a mocked file",
        loading: false,
      };
    },
  };
});

The complete test file would be:

import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import FileViewer from "../FileViewer";

const mockResponse = {
  fileData: "This is a mocked file",
  loading: false,
};
jest.mock("../fileView", () => {
  return {
    useFilesData: () => {
      return {
        fileData: "This is a mocked file",
        loading: false,
      };
    },
  };
});

describe("File Viewer", () => {
  it("display the file heading", async () => {
    render(<FileViewer fileUrl="" filename="regex-tutorial.md" className="" />);
    const paragraphEl = await screen.findByRole("fileHeadingDiplay");
    expect(paragraphEl).toHaveTextContent("regex-tutorial.md");
  });
}
Brunobruns answered 22/12, 2021 at 11:4 Comment(3)
How would you share the mock between tests? Any idea?Pastorale
What is the point of mockResponse variable? You don't use it anywhere?Angulo
This has really helped me. Thanks a millionConform
A
1

In my case I resolved it doing this:

jest.mock('pathToTheHookFile/useClientRect');

describe('It tests the hook', ()=> {
  beforeEach(() => {
    useClientRect.mockReturnValue([300, 200, jest.fn()])
  });

  it('Tests something', ()=> {
    ...
  }
});
Agace answered 15/8, 2023 at 2:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.