Test a React App with Jest and React Testing Library

·

3 min read

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.

  1. 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;
    
  2. 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.

  3. 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);
  })
});