Test a React App with Jest and React Testing Library
Introduction
Jest is a JavaScript test runner that provides resources for writing and running tests. React Testing Library offers a set of testing helpers that structure your tests based on user interactions rather than components’ implementation details. Both Jest and React Testing Library come pre-packaged with Create React App and adhere to the guiding principle that testing apps should resemble how the software will be used.
Step 1 — Setting up the Project
Using tailwindcss for style and fakerJS to establish the project.
Here is the folder structure.
There are several files to show the product card.
ProductCard.js
function ProductCard({ product: { name, price, material, color } }) { return ( <div className="col-span-1 flex flex-col"> <div role="img" className="object-cover w-full h-36 transition duration-500 group-hover:scale-105" style={{ backgroundColor: color }} /> <div className="p-3 bg-white border border-gray-100 shadow flex-grow flex flex-col"> <span className="rounded whitespace-nowrap bg-slate-100 px-3 py-1.5 text-xs font-medium self-start"> {material} </span> <h3 className="mt-4 font-medium text-gray-900">{name}</h3> <p className="my-1.5 text-sm text-gray-700">${price}</p> <div className="flex-grow flex flex-col justify-end"> <form onSubmit={(e) => e.preventDefault()}> <button className="block w-full p-2 text-sm font-medium transition bg-yellow-400 rounded hover:scale-105"> Add to Cart </button> </form> </div> </div> </div> ); } export default ProductCard;
ProductList.js
import useProducts from "../hooks/useProducts"; import ProductCard from "./ProductCard"; function ProductList() { const { products, isLoading, fetchNextPage } = useProducts({ limit: 6 }); const renderedProducts = products.map((product) => { return <ProductCard key={product.id} product={product} />; }); return ( <div className="p-4"> <div className="grid grid-cols-2 grid-flow-row gap-8 sm:grid-cols-3"> {renderedProducts} </div> <div className="text-center text-xl my-8"> <button disabled={isLoading} className="p-4 rounded border-2" onClick={() => !isLoading && fetchNextPage()} > {isLoading ? "Loading..." : "Load More"} </button> </div> </div> ); } export default ProductList;
Another important code is use hooks, which also uses swr and faker.
useProducts.js
import { faker } from "@faker-js/faker/locale/en"; import { useState } from "react"; import useSWR from "swr"; const LIMIT = 6; let cache = []; const productsFetcher = async () => { await pause(800); const nextPage = new Array(LIMIT).fill(0).map(() => { return { id: faker.datatype.uuid(), name: faker.commerce.productName(), price: faker.commerce.price(), material: faker.commerce.productMaterial(), description: faker.commerce.productDescription(), color: faker.color.rgb({ prefix: "#", casing: "lower" }) }; }); cache = [...cache, ...nextPage]; return cache; }; const useProducts = () => { const [page, setPage] = useState(0); const { isLoading, data } = useSWR( `/products/?page=${page}`, productsFetcher, { revalidateIfStale: false, revalidateOnFocus: false, shouldRetryOnError: false, keepPreviousData: true } ); return { fetchNextPage: () => setPage((p) => p + 1), products: data || [], isLoading }; }; const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export default useProducts;
Step 2 — Testing the App
Test the products number and load more button.
import { render, screen ,waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
import App from './App';
test('shows 6 products by default', async () => {
render(<App />);
const headings = await screen.findAllByRole('heading');
expect(headings).toHaveLength(6);
});
test('clicking on the button loads 6 more products', async () => {
render(<App />);
const button = await screen.findByRole("button", {
name: /load more/i
});
await user.click(button);
await waitFor(async () => {
const headings = await screen.findAllByRole('heading');
expect(headings).toHaveLength(12);
})
});