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.tsx
.Add the setup code below to test the component.
src\projects\__tests__\ProjectCard-test.tsx
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: Project;let handleEdit: jest.Mock;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.tsx
Testing a Prop
Open a command-prompt or terminal and run the following command to install
user-event
from the core testing library behind React testing library.npm install --save-dev @testing-library/user-event @testing-library/domTest that the project property renders correctly.
src\projects\__tests__\ProjectCard-test.tsx
- Verify the test passes.PASS src/projects/__tests__/ProjectCard-test.tsx...✓ renders project properly
Testing a Function Prop
Open a command-prompt or terminal and run the following command to install
user-event
from the core testing library behind React testing library.npm install --save-dev @testing-library/user-event @testing-library/domTest that the handler prop is called when edit is clicked.
src\projects\__tests__\ProjectCard-test.tsx
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: Project;let handleEdit: jest.Mock;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.tsxRefactor the
ProjectCard-test.tsx
to use use asetup
function 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: Project;let handleEdit: jest.Mock;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-conventionconst 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.tsx
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: Project;let handleEdit: jest.Mock;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