I'm trying to unit test my custom React hook. It uses react-hook-form's useFormContext
hook to retrieve the isValid
state.
Here's a code snippet example:
import { useFormContext } from "react-hook-form";
export function useCustomHook() {
const { formState: { isValid } } = useFormContext();
function functionThatUsesIsValid() {
// code here...
}
return { functionThatUsesIsValid }
}
I've written two tests using Jest – one that validates the behaviour of functionThatUsesIsValid
whenever isValid
is true, another when it's false. It's a void function, so I'm not testing its return value(s), but rather checking that it calls what I expect it to call.
To do this, I'm using react-hooks-testing-library's renderHook
method. I've mocked react-hook-form using jest.mock
, meaning it's hoisted to the top of the test file.
jest.mock("react-hook-form", () => ({
useFormContext: jest.fn().mockReturnValue({
formState: { isValid: true },
})
}))
it("custom hook behaves correctly when form is valid", () => {
const { result: { current: { functionThatUsesIsValid } } } = renderHook(useCustomHook);
functionThatUsesIsValid();
// expect things...
})
it("custom hook behaves correctly when form is invalid", () => {
const { result: { current: { functionThatUsesIsValid } } } = renderHook(useCustomHook);
functionThatUsesIsValid();
// expect different things, since behaviour should be different...
})
This works lovely for the first test, but the second fails because isValid
is still mocked to true. I want to set a different value (false) for the second test.
Given this, I try jest.doMock
within each test, like so:
it("custom hook behaves correctly when form is invalid", () => {
jest.doMock("react-hook-form", () => ({
useFormContext: jest.fn().mockReturnValue({
formState: { isValid: false }
})
}))
const { result: { current: { functionThatUsesIsValid } } } = renderHook(useCustomHook);
functionThatUsesIsValid();
// ...
})
This doesn't work, and it's clear the test is calling the real useFormContext
rather than a mock. The examples I can find of jest.doMock
seem to involve calling the mocked module directly within the test, which isn't what I'm trying to do. Using jest.spyOn
presents the same problem.
How can I make this work, and get both of my tests passing?
Figured it out. You can do the following (in JavaScript):
jest.mock("react-hook-form",() => ({
useFormContext: jest.fn(),
}))
it("custom hook behaves correctly when form is invalid", () => {
useFormContext.mockReturnValue({
formState: { isValid: false }
})
// rest of the test...
});
You're turning that function into a generic mock straight away, and since it's now a Jest mock, you can invoke mockReturnValue
from within the test.
If you're using TypeScript like me, you might try something like useFormContext = jest.fn().mockReturnValue(...)
within the test, and find that TS complains because you're reassigning an import. This gets round it, since you're not reassigning anything.
Note that with TypeScript, you'll also need to say (useFormContext as jest.Mock).mockReturnValue(...)
before it's happy.