We are going to start by creating a unit test to verify that submitting the Contact Us form without filling in the fields results in errors being displayed on the page:
- We are going to implement a unit test on the ContactUs component. We'll start by creating a file called ContactUs.test.tsx in the src folder.
- We are going to use ReactDOM to render a test instance of the ContactUs component. Let's import React and ReactDOM:
import React from "react";
import ReactDOM from "react-dom";
- We are going to simulate the form submit event, so let's import the Simulate function from the React testing utilities:
import { Simulate } from "react-dom/test-utils";
- Let's now import the component we need to test:
import ContactUs from "./ContactUs";
- We also need to import the submission result interface from Form.tsx as well:
import { ISubmitResult } from "./Form";
- Let's start to create our test using the Jest test function, with the results outputting to a ContactUs group:
describe("ContactUs", () => {
test("When submit without filling in fields should display errors", () => {
// TODO - implement the test
});
});
- The first task in our test implementation is to create our React component in the DOM:
test("When submit without filling in fields should display errors", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const container = document.createElement("div");
ReactDOM.render(<ContactUs onSubmit={handleSubmit} />, container);
// TODO - submit the form and check errors are shown
ReactDOM.unmountComponentAtNode(container);
});
First, we create a container div tag and then render our ContactUs component into this. We have also created a handler for the onSubmit prop, which returns success. The last line in the test cleans up by removing the DOM elements that were created in the test.
- Next, we need to get a reference to the form, and then submit it:
ReactDOM.render(<ContactUs onSubmit={handleSubmit} />, container);
const form = container.querySelector("form");
expect(form).not.toBeNull();
Simulate.submit(form!);
// TODO - check errors are shown
ReactDOM.unmountComponentAtNode(container);
Here is the step-by-step description:
- We use the querySelector function, passing in the form tag to get a reference to the form tag.
- We then check that the form is not null by using the Jest expect function with the not and toBeNull functions chained together.
- The submit event is simulated using the Simulate function from the React testing utilities. We use an ! after the form variable to inform the TypeScript compiler that it is not null.
- Our final task is to check that the validation errors are displayed:
Simulate.submit(form!);
const errorSpans = container.querySelectorAll(".form-error");
expect(errorSpans.length).toBe(2);
ReactDOM.unmountComponentAtNode(container);
Let's see this step-by-step:
- We use the querySelectorAll function on the container DOM node, passing in a CSS selector to find the span tags that should contain the errors
- We then use the Jest expect function to verify that two errors are displayed
- When the test runs, it should pass successfully, giving us two passing tests:
In this test, Jest is rendering the component in a fake DOM. The form submit event is also simulated, using the simulate function from standard React testing utilities. So, there's a lot of mocking going on in order to facilitate an interactive component test.
Also note that we are referencing internal implementation details in our test code. We reference a form tag, along with a form-error CSS class. What if we later change this CSS class name to contactus-form-error? Our test would break, without there necessarily being a problem with our app.
This is called a false positive, and can make code bases with these kinds of tests very time-consuming to change.