I recently ran into quite a bit of frustration attempting to test a component that wrapped a <Controller /> from React-Hook-Form. Most of my projects use MUI as the frontend component library so I’ve been getting used to “uncontrolling” my inputs with RHF by making my own set of inputs that can easily go into new projects. I’ve made a basic set of components that wrap the <Controller /> around each MUI input type so that I can pass a name, label, some options, and the critical { control } prop from useForm().
This works great since I’ll have a parent form component that contains the useForm() hook and contains { control }, but when testing these input wrappers individually there’s the issue of calling that hook. Since hooks can only be called from inside a component you can’t just run that in the test setup – I’ve seen plenty of mocking strategies online and they all seem a bit too convoluted for what should be a simple solution.
My current solution is annoyingly simple, and works well enough for my testing needs. I can’t remember which stackoverflow post I found it, but I now declare a new <Component /> inside my test which can call useForm() and create the input I’m looking for. This can then be passed to render() for the test and everything works smoothly after that. For example:
describe('TextInput', () => {
it('renders the TextInput component', () => {
const Component = () => {
const { control } = useForm();
return (
<TextInput
name="test"
label="Test"
control={control}
/>
);
};
render(<Component />)
expect(screen.getByLabelText("Test")).toBeDefined()
})
})
This seems like a silly solution, so hopefully by posting it here someone will see it and go “hey, that’s a dumb way” and correct my strategy. I don’t get to see any of the control activities that I would be able to by mocking but I also haven’t run into any situations yet where I would need to.
It at least works enough for now to keep my CI/CD pipeline happy without shying away from test coverage.