Testing react component that uses react-hook-form

React hook form is one of the most poplar library used to create forms in react components. It drastically reduces the amount of code that one has to write for creating forms and writing validations.

You would find ample amount articles and blogs all over the internet on how to use this library. Their own documentation is also well written and easy to follow and understand. But, this post is about testing the component that uses react hook form.

Coming straight to the point, consider a basic form to reset your password. It’s a simple form with two fields — New password and Confirm new password. With the basic usage of react hook form, we are using one more function from the library i.e. watch. It basically watches the new password field and its value is compared with confirm new password field so that we have a validation in place that ensures both the password field values are exactly same.

import React from "react";
import { useForm } from "react-hook-form";
import apiCall from './lib/apiCall';
export default function ResetPassword() {
const { register, errors, handleSubmit, watch } = useForm();
const newPassword = watch("newPassword");
const onSubmit = async data => {
await apiCall(data);
};
return (
<div>
<h1>Reset password example</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<label>New password</label>
<input
name="newPassword"
placeholder="New password"
defaultValue=""
type="password"
ref={register({
required: "New password is required"
})}
/>
{errors.newPassword && <p>{errors.newPassword.message}</p>}
<label>Confirm new password</label>
<input
name="confirmNewPassword"
placeholder="Confirm new password"
defaultValue=""
type="password"
ref={register({
required: "Confirm new password is required",
validate: value =>
newPassword === value
? true
: "New password and confirm new password does not match"
})}
/>
{errors.confirmNewPassword && (
<p>{errors.confirmNewPassword.message}</p>
)}
<input type="submit" />
</form>
</div>
);
}

Now, let’s write some tests. We would write below tests for now,
- Should watch input correctly
- Should display correct error message for password miss match
- Should submit form successfully

The only prerequisite you need for testing react hook form is to import `mutationobserver-shim` in your test setup file.

import React from "react";
import ResetPassword from "./ResetPassword";
import { render, act, fireEvent, cleanup } from "@testing-library/react";
import apiCall from "./lib/apiCall";
jest.mock("./lib/apiCall");const mockValue = "Test@123";
const mockPostData = {
newPassword: mockValue,
confirmNewPassword: mockValue
};
afterEach(cleanup);test("should watch input correctly", () => {
const { container } = render(<ResetPassword />);
const newPassword = container.querySelector(
"input[name='newPassword']"
);
const confirmNewPassword = container.querySelector(
"input[name='confirmNewPassword']"
);
fireEvent.input(newPassword, {
target: {
value: mockValue
}
});
fireEvent.input(confirmNewPassword, {
target: {
value: mockValue
}
});
expect(newPassword.value).toEqual(mockValue);
expect(confirmNewPassword.value).toEqual(mockValue);
});
test("should display correct error message for password miss match", async () => {
const { container } = render(<ResetPassword />);
const newPassword = container.querySelector(
"input[name='newPassword']"
);
const confirmNewPassword = container.querySelector(
"input[name='confirmNewPassword']"
);
const submitButton = container.querySelector(
"input[type='submit']"
);
fireEvent.input(newPassword, {
target: {
value: mockValue
}
});
fireEvent.input(confirmNewPassword, {
target: {
value: `${mockValue}4`
}
});
await act(async () => {
fireEvent.submit(submitButton);
});
expect(container.textContent).toMatch(
/New password and confirm new password does not match/
);
});
test("Should submit form successfully", async () => {
apiCall.mockImplementationOnce(() => Promise.resolve(true));
const { container } = render(<ResetPassword />);
const newPassword = container.querySelector(
"input[name='newPassword']"
);
const confirmNewPassword = container.querySelector(
"input[name='confirmNewPassword']"
);
const submitButton = container.querySelector(
"input[type='submit']"
);
fireEvent.input(newPassword, {
target: {
value: mockValue
}
});
fireEvent.input(confirmNewPassword, {
target: {
value: mockValue
}
});
await act(async () => {
fireEvent.submit(submitButton);
});
expect(apiCall).toHaveBeenCalledWith(mockPostData);
});
fireEvent.input: You should always use this function to set value for form inputs. Normal DOM set value won’t work.// Won't work
newPassword.value = "Test@123";
// However, this would also work
fireEvent.blur(newPassword, {
target: {
value: mockValue
}
});
await act(async () => {}): To prepare a component for assertions, wrap the code performing updates inside an act() call. This makes your test run closer to how React works in the browser. All validation methods in react hook form are treated as async functions, so it's important to wrap the act around async.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store