Testing Lab 3: More Component Testing
Objectives
- Test Setup
- Testing a Prop
- Testing a Function Prop
- Taking a Snapshot
Steps
Test Setup
- Create the directory - src\projects\__tests__.
- Create the file - src\projects\__tests__\ProjectCard-test.js.
- Add the setup code below to test the component. - src\projects\__tests__\ProjectCard-test.js
import { render, screen } from "@testing-library/react";
import React from "react";
import { Project } from "../Project";
import ProjectCard from "../ProjectCard";
describe("<ProjectCard />", () => {
  let project;
  let handleEdit;
  beforeEach(() => {
    project = new Project({
      id: 1,
      name: "Mission Impossible",
      description: "This is really difficult",
      budget: 100,
    });
    handleEdit = jest.fn();
  });
  it("should initially render", () => {
    render(<ProjectCard project={project} onEdit={handleEdit} />);
  });
});
- Verify the test fails. - useHref() may be used only in the context of a <Router> component.
- Wrap the component in a - MemoryRouter.- import { render, screen } from '@testing-library/react';
 import React from 'react';
 + import { MemoryRouter } from 'react-router-dom';
 import { Project } from '../Project';
 import ProjectCard from '../ProjectCard';
 describe('<ProjectCard />', () => {
 let project;
 let handleEdit;
 beforeEach(() => {
 project = new Project({
 id: 1,
 name: 'Mission Impossible',
 description: 'This is really difficult',
 budget: 100,
 });
 handleEdit = jest.fn();
 });
 it('should initially render', () => {
 render(
 + <MemoryRouter>
 <ProjectCard project={project} onEdit={handleEdit} />
 + </MemoryRouter>
 );
 });
 });
<MemoryRouter>- is a<Router>that keeps the history of your "URL" in memory (does not read or write to the address bar). Useful in tests and non-browser environments like React Native.
- Verify the initial test now passes.PASS src/projects/__tests__/ProjectCard-test.js
Testing a Prop
- Open a command-prompt or terminal and run the following command to install - user-eventfrom the core testing library behind React testing library.- npm install --save-dev @testing-library/user-event @testing-library/dom
- Test that the project property renders correctly. - src\projects\__tests__\ProjectCard-test.js
...
describe('<ProjectCard />', () => {
  let project;
  let handleEdit;
  beforeEach(() => {
    project = new Project({
      id: 1,
      name: 'Mission Impossible',
      description: 'This is really difficult',
      budget: 100,
    });
    handleEdit = jest.fn();
  });
  it('should initially render', () => {
    render(
      <MemoryRouter>
        <ProjectCard project={project} onEdit={handleEdit} />
      </MemoryRouter>
    );
  });
+  it('renders project properly', () => {
+    render(
+      <MemoryRouter>
+        <ProjectCard project={project} onEdit={handleEdit} />
+      </MemoryRouter>
+    );
+    expect(screen.getByRole('heading')).toHaveTextContent(project.name);
+    // screen.debug(document);
+    screen.getByText(/this is really difficult\.\.\./i);
+    screen.getByText(/budget : 100/i);
+  });
});
- Verify the test passes.PASS src/projects/__tests__/ProjectCard-test.js
 ...
 ✓ renders project properly
Testing a Function Prop
- Open a command-prompt or terminal and run the following commands to install a more recent version of the - @testing-library/user-eventpackage.- npm uninstall @testing-library/user-event
 npm install @testing-library/user-event@14
- Test that the handler prop is called when edit is clicked. - src\projects\__tests__\ProjectCard-test.js- import { render, screen } from '@testing-library/react';
 import React from 'react';
 import { MemoryRouter } from 'react-router-dom';
 import { Project } from '../Project';
 import ProjectCard from '../ProjectCard';
 + import userEvent from '@testing-library/user-event';
 describe('<ProjectCard />', () => {
 let project;
 let handleEdit;
 beforeEach(() => {
 project = new Project({
 id: 1,
 name: 'Mission Impossible',
 description: 'This is really difficult',
 budget: 100,
 });
 handleEdit = jest.fn();
 });
 it('should initially render', () => {
 render(
 <MemoryRouter>
 <ProjectCard project={project} onEdit={handleEdit} />
 </MemoryRouter>
 );
 });
 it('renders project properly', () => {
 render(
 <MemoryRouter>
 <ProjectCard project={project} onEdit={handleEdit} />
 </MemoryRouter>
 );
 expect(screen.getByRole('heading')).toHaveTextContent(project.name);
 // screen.debug(document);
 screen.getByText(/this is really difficult\.\.\./i);
 screen.getByText(/budget : 100/i);
 });
 + it('handler called when edit clicked', async () => {
 + render(
 + <MemoryRouter>
 + <ProjectCard project={project} onEdit={handleEdit} />
 + </MemoryRouter>
 + );
 + // this query works screen.getByText(/edit/i)
 + // but using role is better
 + const user = userEvent.setup();
 + await user.click(screen.getByRole('button', { name: /edit/i }));
 + expect(handleEdit).toBeCalledTimes(1);
 + expect(handleEdit).toBeCalledWith(project);
 + });
 });
- Verify the test passes. - PASS src/projects/__tests__/ProjectCard-test.js
- Refactor the - ProjectCard-test.jsto use use a- setupfunction to render the component at the start of each test.- import { render, screen } from "@testing-library/react";
 import React from "react";
 import { MemoryRouter } from "react-router-dom";
 import { Project } from "../Project";
 import ProjectCard from "../ProjectCard";
 import userEvent from "@testing-library/user-event";
 import renderer from "react-test-renderer";
 describe("<ProjectCard />", () => {
 let project;
 let handleEdit;
 const setup = () =>
 render(
 <MemoryRouter>
 <ProjectCard project={project} onEdit={handleEdit} />
 </MemoryRouter>
 );
 beforeEach(() => {
 project = new Project({
 id: 1,
 name: "Mission Impossible",
 description: "This is really difficult",
 budget: 100,
 });
 handleEdit = jest.fn();
 });
 it("should initially render", () => {
 setup();
 });
 it("renders project properly", () => {
 setup();
 expect(screen.getByRole("heading")).toHaveTextContent(project.name);
 // screen.debug(document);
 screen.getByText(/this is really difficult\.\.\./i);
 screen.getByText(/budget : 100/i);
 });
 it("handler called when edit clicked", async () => {
 setup();
 // this query works screen.getByText(/edit/i)
 // but using role is better
 // eslint-disable-next-line testing-library/render-result-naming-convention
 const user = userEvent.setup();
 await user.click(screen.getByRole("button", { name: /edit/i }));
 expect(handleEdit).toBeCalledTimes(1);
 expect(handleEdit).toBeCalledWith(project);
 });
 test("snapshot", () => {
 const tree = renderer
 .create(
 <MemoryRouter>
 <ProjectCard project={project} onEdit={handleEdit} />
 </MemoryRouter>
 )
 .toJSON();
 expect(tree).toMatchSnapshot();
 });
 });- Why not just put the setup code in the - beforeEach? See this ESLint rule for react-testing-library.
Taking a Snapshot
- Take a snapshot of the component. - src\projects\__tests__\ProjectCard-test.js- import { render, screen } from '@testing-library/react';
 import React from 'react';
 import { MemoryRouter } from 'react-router-dom';
 import { Project } from '../Project';
 import ProjectCard from '../ProjectCard';
 import userEvent from '@testing-library/user-event';
 + import renderer from 'react-test-renderer';
 describe('<ProjectCard />', () => {
 let project;
 let handleEdit;
 beforeEach(() => {
 ...
 });
 ...
 + test('snapshot', () => {
 + const tree = renderer
 + .create(
 + <MemoryRouter>
 + <ProjectCard project={project} onEdit={handleEdit} />
 + </MemoryRouter>
 + )
 + .toJSON();
 + expect(tree).toMatchSnapshot();
 + });
 });
- Verify the snapshot is taken. - ✓ 1 snapshot written