97

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?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Homa
  • 3,417
  • 2
  • 19
  • 24

4 Answers4

191

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
});
User 1058612
  • 3,739
  • 1
  • 27
  • 33
Homa
  • 3,417
  • 2
  • 19
  • 24
  • 64
    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 at – nbpeth Jul 23 '20 at 17:09
  • For me it gives the error that "useClientRect is not a function undefined given instead" – Muhammad Haseeb Dec 29 '20 at 13:14
  • 2
    @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. – Homa Dec 30 '20 at 23:24
  • @Homa yeah I put my hook name but my hook returns and object – Muhammad Haseeb Jan 02 '21 at 18:38
  • 2
    @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()}));`. – Homa Jan 02 '21 at 20:29
  • What should be the `module_name`? – Mohammad Kermani Mar 15 '21 at 17:35
  • 2
    @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. – Homa Mar 15 '21 at 23:03
  • 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. – stephen.hanson Mar 23 '22 at 03:34
  • 1
    full file code would be much helpful. – Sushmit Sagar Jun 23 '22 at 17:47
  • I facing issues when the custom hook is default exported. How do I spy on it's returned values in different scenarios? – Sushmit Sagar Jun 23 '22 at 18:20
  • Does this work if you are passing an argument with the hook? – ShashankAC Aug 03 '23 at 07:56
43

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.MockedFunction 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 = useClientRect as jest.MockedFunction<typeof 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
  });
});
toakleaf
  • 909
  • 8
  • 11
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");
  });
}
fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
murtaza salim
  • 171
  • 1
  • 7
0

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', ()=> {
    ...
  }
});