Background
So I am trying to test a React functional component that calls a custom hook and depending on the return value of the hook, the component will render something differently. See below for an example:
// App.js
import "./styles.css";
import useCustomHook from "./useHook";
export default function App() {
const [state, handler] = useCustomHook();
return state ? <div>state is true</div> : <div>state is false</div>;
}
// useHook.js
import { useState } from "react";
const useCustomHook = () => {
const [myState, setMyState] = useState(false);
const handleSetMyState = () => {
return setMyState((prevState) => !prevState);
};
return [myState, handleSetMyState];
};
export default useCustomHook;
and finally, my test:
import "@testing-library/jest-dom";
import { render, screen, cleanup } from "@testing-library/react";
import App from "./App";
import useCustomHook from './useHook';
jest.mock("./useHook", () => ({
__esModule: true,
default: () => [false, jest.fn()]
}));
describe("testing App state", () => {
afterEach(() => {
jest.clearAllMocks();
cleanup();
});
it("should render false when state is false", () => {
// initialized as false AND mocked returned as false
render(<App />);
expect(screen.queryByText(/false/i)).toBeInTheDocument();
});
it("should render true when state is true", () => {
// the following doesn't work
useCustomHook.mockImplementation(() => [ true, jest.fn() ]) // outputs error: TypeError: _useCustomHook.default.mockImplementation is not a function
render(<App />);
expect(screen.queryByText(/true/i)).toBeInTheDocument();
});
});
Now the test would pass in my local environment ( unfortunately codesandbox's environment has issues with jest.mock, so I cannot provide a live sample ) but for the next test case, if the state returned is true the component should render text with "true" in it.
Issue
However, I am unable to change the mocked implementation or the returned value of the mock for it to return something like [true, jest.fn()]
So I have been stuck on this issue for some time now and cannot find any relatable resource online that fits as a solution. I have tried the following and none worked.
- importing the custom hook in test and changing its mocked implementation ( like shown in example )
- same as above but changing its mockReturnValueOnce to
[ true, jest.fn() ]
- adding a
__mocks__
directory in the same lvl as the hook and having a file with the same name__mocks__/useHook.js
, and mock the whole module byjest.mock('./useHook')
instead. Similar to this solution. - (updated, also tried this as latest attempt to mock)
jest.mock('./useHook', () => ({ __esModule: true, default: {useCustomHook: jest.fn()}}))
and it still outputs the same error when I tried to change the implementationTypeError: _useCustomHook.default.mockImplementation is not a function
Please help, something as simple as changing a mocked returned value is done so easily with other languages and testing frameworks. Why is jest complaining about?
CodePudding user response:
The default export is not a mock function, you should use jest.fn()
to create a mock function with a mock implementation.
jest.mock('./useHook', () => ({
__esModule: true,
// It should be
default: jest.fn(() => [false, jest.fn()]),
// NOT
// default: () => [false, jest.fn()]
}));
But test implementation details are not encouraged. See What you should avoid with Testing Library
Testing Library encourages you to avoid testing implementation details like the internals of a component you're testing (though it's still possible). The Guiding Principles of this library emphasize a focus on tests that closely resemble how your web pages are interacted by the users.
You may want to avoid the following implementation details:
- Internal state of a component
- Internal methods of a component
- Lifecycle methods of a component
- Child components
CodePudding user response:
I was able to find a workaround instead.
// test.js
import * as useCustomHook from './useHook';
// then inside my individual test
it('should render true when state is true"', () => {
const mockHook = jest.spyOn(useCustomHook, 'default');
mockHook.mockImplementation(() => [true, jest.fn()]);
render(<App />);
expect(screen.queryByText(/true/i)).toBeInTheDocument();
});
Not sure why jest.mock
doesn't work, but I was able to make it work with jest.spyOn
. If anyone is able to shed some light on why one method worked and the other didn't, it would be greatly appreciated.