Testing React
Part 1, Chapter 5
Let's look at testing React components.
Test Framework
To test our application, we need a test framework. Some of the test frameworks for React are:
While Jest is the most popular, it can be a bit difficult to configure since it's support for ECMAScript Modules (ESM) is (as of writing) experimental. So, in this course, we'll use Vitest, which is a powerful test framework for modern web applications. It supports all of the modern JavaScript features, like ESM, TypeScript, and JSX. Plus, it leverages Vite, so using it with our application is simple since we also use it.
Start by installing Vitest and saving it as a development dependency:
$ npm install [email protected] --save-dev
Next, in services/client/package.json, add "test": "vitest"
and "test:ui": "vitest --ui"
to the scripts
section:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui"
},
Now, let's create a basic test to ensure Vitest is configured correctly. Add a new folder called "tests" to "services/client/src". Then, add a main.test.ts file to that newly created folder:
import { it, expect, describe } from "vitest";
describe("group", () => {
it("should", () => {
expect(1).toBeTruthy();
});
});
Run the test:
$ npm test
You should see something similar to:
✓ src/tests/main.test.ts (1)
✓ group (1)
✓ should
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 09:36:01
Duration 279ms (transform 24ms, setup 0ms, collect 18ms, tests 3ms, environment 0ms, prepare 53ms)
PASS Waiting for file changes...
press h to show help, press q to quit
Press q to close the test. With Vitest, you can also view the output and interact with tests from the browser:
$ npm run test:ui
Testing Components
To test React components we need a few tools.
React Testing Library
The first one is React Testing Library, a fantastic yet surprisingly simple testing library made specifically for testing React components.
Install it:
$ npm install @testing-library/[email protected] --save-dev
For more on setting up React Testing Library, review the official docs along with the excellent Modern React testing, part 3: Jest and React Testing Library article.
jsdom
The next dependency is jsdom. Why do we need it? By default, our tests run in a Node environment that's not aware of browser APIs. So, to be able to run tests for our components we need a way to emulate the browser environment, which is what jsdom helps with.
Install it:
$ npm install [email protected] --save-dev
Next, we need to tell Vitest to use jsdom as the testing environment for our project. To do that, in the root of the "client" folder, add a new file called vitest.config.ts:
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
},
});
Run the tests again to ensure everything is still wired up correctly:
$ npm test
Vite Plugin React
Next, let's add Vite Plugin React.
Install:
$ npm install @vitejs/[email protected] --save-dev
Update vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react"; // new
export default defineConfig({
plugins: react(), // new
test: {
environment: "jsdom",
},
});
jest-dom
The last library is jest-dom, which makes it easier to write assertions against elements on the DOM.
Install it:
$ npm install @testing-library/[email protected] --save-dev
With that, we can start writing some tests!
Test Utils
To make our tests easier to write, let's create a test utils file. Add a new file called test-utils.tsx to "services/client/src/tests":
import React, { ReactElement } from "react";
import { render, RenderOptions } from "@testing-library/react";
import { ChakraProvider } from "@chakra-ui/react";
import { MemoryRouter } from "react-router-dom";
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
return (
<ChakraProvider>
<MemoryRouter>
{children}
</MemoryRouter>
</ChakraProvider>
);
};
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, "wrapper">
) => render(ui, { wrapper: AllTheProviders, ...options });
export * from "@testing-library/react";
export { customRender as render };
We'll talk about react-router-dom later in the course, but for now go ahead and install it:
$ npm install [email protected]
Tests
Start by adding a "components" folder to "services/client/src/tests". Then within "components", add a new file called Users.test.tsx:
import { it, expect, describe } from "vitest";
import { render, screen } from "../test-utils";
import "@testing-library/jest-dom/vitest";
import Users from "../../components/Users";
describe("Users", () => {
it("Should render no registered users when there are no users passed to the component", () => {
render(<Users users={[]} />);
const message = screen.getByText(/no registered users/i);
expect(message).toBeTruthy();
});
});
So, in this test, we asserted, via a regular expression, that no registered users
is found on the DOM when the API responds with an empty array.
Ensure the tests pass via npm test
:
✓ src/tests/main.test.ts (1)
✓ src/tests/components/Users.test.tsx (1)
Test Files 2 passed (2)
Tests 2 passed (2)
Start at 14:03:45
Let's add another test to the same describe
block:
it("Should render user details when users are passed to the component", () => {
const mockUsers = [
{
id: 1,
username: "john_doe",
email: "[email protected]",
created_date: "2024-08-19",
},
{
id: 2,
username: "jane_doe",
email: "[email protected]",
created_date: "2024-08-18",
},
];
render(<Users users={mockUsers} />);
// Assert that the user details are correctly rendered
const userOne = screen.getByText("john_doe");
const userTwo = screen.getByText("jane_doe");
expect(userOne).toBeInTheDocument();
expect(userTwo).toBeInTheDocument();
const emailOne = screen.getByText("[email protected]");
const emailTwo = screen.getByText("[email protected]");
expect(emailOne).toBeInTheDocument();
expect(emailTwo).toBeInTheDocument();
});
Here, we passed two users to the component and ran assertions against the virtual DOM via jsdom.
Ensure the new test passes.
When you write tests, keep in mind to test how the component renders and responds to the end user's actions. Don't test the underlying implementation since any changes to the implementation will break your tests.
Test Coverage
Curious about test coverage? To get the coverage report, add another script to the scripts
section of package.json:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage"
},
Run the tests with coverage:
$ npm run coverage
You should see something like:
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 50 | 66.66 | 33.33 | 50 |
src | 0 | 0 | 0 | 0 |
App.tsx | 0 | 0 | 0 | 0 | 1-40
main.tsx | 0 | 0 | 0 | 0 | 1-13
src/components | 100 | 100 | 100 | 100 |
Users.tsx | 100 | 100 | 100 | 100 |
----------------|---------|----------|---------|---------|-------------------
You can also view this in your browser by opening up the index.html file from the "services/client/coverage" folder. Make sure to add this folder to your .gitignore file.
We have 100% coverage for our Users
component, but we're missing some things in the App
component. See if you can boost the test coverage on your own.
Testing Interactions
React Testing Library can also be used to test user interactions. We can fire events and then test that the actual results are the same as the expected results. We'll look at this in a future chapter.
✓ Mark as Completed