This is the 7th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.

What is a TDD

TDD (test-driven Development) is a core practice and technology in agile development, as well as a software design methodology.

The idea is that we write test cases before we write code, and the tests determine our code. Moreover, TDD is more about writing independent test cases, such as testing only a function point of a component, a tool function, etc.

The process of TDD

  • Write test cases
  • Run the test. The test failed
  • Modify the code
  • The test pass
  • Refactor/optimize the code
  • Repeat the preceding steps to add new functions

At some point, it may seem tedious or impractical to a beginner, but doing this rigorously and letting you decide for yourself whether a test case will help your component makes sense.

This article will demonstrate how to implement test-driven development in React by creating a Confirmation component.

The Confirmation component features:

  • Confirmation title
  • Validation description – Receives problems that external programs want to validate
  • An acknowledgement button that supports external callback functions
  • A cancel button that supports external callback functions

Neither button knows what to do next when it is clicked, because it is outside the responsibility of the component, but the component should receive callback events for those clicked buttons. Start with a design:

So, let’s get started.

The test component

First, initialize a React project with create-react-app. Cra has built in @testing-library/ React as a testing framework.

npx create-react-app my-react-app
Copy the code

Let’s start with the test file. Create the component’s directory “Confirmation” and add an “index.test.js” file to it.

Ensure rendering tests

The first test is quite abstract. Just check if the component presents (anything) to make sure the component exists. But in fact, the component I’m going to test doesn’t exist yet.

First, use the getByRole method to find if the role attribute is found in the dialog document.

The role attribute may not be very common, and can be used when existing HTML tags do not adequately express semantics. For example, when clicking on a button, role=”button”; Will make this element clickable; You can also use the role attribute to tell auxiliary devices (such as screen readers) what role the element plays.

import React from 'react';
import { render } from '@testing-library/react';

describe('Confirmation component'.() = > {
   it('should render'.() = > {
       const {getByRole} = render(<Confirmation />);
       expect(getByRole('dialog')).toBeInTheDocument();
   });
});
Copy the code

Run tests and listen

yarn test --watch
Copy the code

Thinking with “toes” knows it must be different to pass the test. Next, let’s create a component that is sufficient for this test:

import React from 'react';

const Confirmation = () = > {
   return <div role="dialog"></div>;
};

export default Confirmation;
Copy the code

Then import the component into the test, and it now passes.

Next, the component should have a dynamic title.

Dynamic heading test

Create a test case:

it('should have a dynamic title'.() = > {
    const title = 'title';
    const { getByText } = render(<Confirmation title={title} />);
    expect(getByText(title)).toBeInTheDocument();
});
Copy the code

Test failed, modify code to make it pass:

import React from "react";

const Confirmation = ({ title }) = > {
  return (
    <div role="dialog">
      <h1>{title}</h1>
    </div>
  );
};

export default Confirmation;
Copy the code

Next up, there is a confirmation problem prompt in this component.

Dynamic problem testing

The problem is also dynamic so that it can be passed in from outside the component.

it('should have a dynamic confirmation question'.() = > {
       const question = 'Do you confirm? ';
       const {getByText} = render(<Confirmation question={question} />);
       expect(getByText(question)).toBeInTheDocument();
   });
Copy the code

Test fails again, modify code to make it pass:

import React from 'react';

const Confirmation = ({title, question}) = > {
   return (
       <div role="dialog">
           <h1>{title}</h1>
           <div>{question}</div>
       </div>
   );
};


export default Confirmation;
Copy the code

Confirm button test

Next is the confirm button test. We first check to see if there is a button on the component that says “OK.”

Write test case code:

it('should have an "OK" button'.() = > {
       const {getByRole} = render(<Confirmation />);
       expect(getByRole('button', {name: 'confirm'})).toBeInTheDocument();
   });
Copy the code

We use the name option here because we know there is at least one more button in the component and need to be more specific about which button to look up the assertion for

Component code:

import React from 'react';

const Confirmation = ({title, question}) = > {
   return (
       <div role="dialog">
           <h1>{title}</h1>
           <div>{question}</div>
           <button>confirm</button>
       </div>
   );
};

export default Confirmation;
Copy the code

Cancel button test

Do the same for the Cancel button:

Testing:

it('Should have an' button'.() = > {
       const {getByRole} = render(<Confirmation />);
       expect(getByRole('button', {name: 'cancel'})).toBeInTheDocument();
   });
Copy the code

Component code:

import React from 'react';

const Confirmation = ({title, question}) = > {
   return (
       <div role="dialog">
           <h1>{title}</h1>
           <div>{question}</div>
           <button>confirm</button>
           <button>cancel</button>
       </div>
   );
};

export default Confirmation;
Copy the code

All right. Now that we have the HTML we want for the component’s rendering, I want to make sure I can pass the callback functions for this component’s buttons externally and make sure they are called when the button is clicked.

So I’ll start by testing the “Ok” button:

it('Should be able to receive a handler for the "confirmation" button and execute it upon click'.() = > {
    const onOk = jest.fn();
    const { getByRole } = render(<Confirmation onOk={onOk} />);
    const okButton = getByRole("button", { name: "Confirm" });

    fireEvent.click(okButton);

    expect(onOk).toHaveBeenCalled();
  });
Copy the code

Start by creating a mock function with jest. Fn and passing it to the component as an “onOk” handler, simulating clicking ok and asserting that the function has been called.

This test obviously failed, here is the supplementary code:

import React from "react";

const Confirmation = ({ title, question, onOk }) = > {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button onClick={onOk}>confirm</button>
      <button>cancel</button>
    </div>
  );
};

export default Confirmation;

Copy the code

Next, let’s do the same for the Cancel button:

Testing:

it('Should be able to receive a handler for the "cancel" button and execute it upon click'.() = > {
    const onCancel = jest.fn();
    const { getByRole } = render(<Confirmation onCancel={onCancel} />);
    const okButton = getByRole("button", { name: "Cancel" });

    fireEvent.click(okButton);

    expect(onCancel).toHaveBeenCalled();
  });
Copy the code

Components:

import React from "react";

const Confirmation = ({ title, question, onOk, onCancel }) = > {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button onClick={onOk}>confirm</button>
      <button onClick={onCancel}>cancel</button>
    </div>
  );
};

export default Confirmation;
Copy the code

Here is the full test file:

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Confirmation from "./index";

describe("Confirmation component".() = > {
  it("should render".() = > {
    const { getByRole } = render(<Confirmation />);
    expect(getByRole("dialog")).toBeInTheDocument();
  });

  it("should have a dynamic title".() = > {
    const title = "Title";
    const { getByText } = render(<Confirmation title={title} />);
    expect(getByText(title)).toBeInTheDocument();
  });

  it("should have a dynamic confirmation question".() = > {
    const question = "Do you confirm?";
    const { getByText } = render(<Confirmation question={question} />);
    expect(getByText(question)).toBeInTheDocument();
  });

  it('Should have an' button'.() = > {
    const { getByRole } = render(<Confirmation />);
    expect(getByRole("button", { name: "Confirm" })).toBeInTheDocument();
  });

  it('Should have an' button'.() = > {
    const { getByRole } = render(<Confirmation />);
    expect(getByRole("button", { name: "Cancel" })).toBeInTheDocument();
  });

  it('Should be able to receive a handler for the "confirmation" button and execute it upon click'.() = > {
    const onOk = jest.fn();
    const { getByRole } = render(<Confirmation onOk={onOk} />);
    const okButton = getByRole("button", { name: "Confirm" });

    fireEvent.click(okButton);

    expect(onOk).toHaveBeenCalled();
  });

  it('Should be able to receive a handler for the "cancel" button and execute it upon click'.() = > {
    const onCancel = jest.fn();
    const { getByRole } = render(<Confirmation onCancel={onCancel} />);
    const okButton = getByRole("button", { name: "Cancel" });

    fireEvent.click(okButton);

    expect(onCancel).toHaveBeenCalled();
  });
});
Copy the code

Although this component has no style, or we can optimize and add more functionality, the above steps have repeatedly demonstrated the logic of test-driven development.

TDD guides the specification of component features step by step, ensuring that we follow the logic of existing development when refactoring components or when others modify code. This is the advantage of TDD.

debugging

We can use debug to print the rendered HTML structure

code

it('Should be able to receive a handler for the "cancel" button and execute it upon click'.() = > {
    const onCancel = jest.fn();
    const { getByRole, debug } = render(<Confirmation onCancel={onCancel} />);

    debug();
    
  });
Copy the code

This makes it easy to look up the DOM.

summary

There are also many convenient apis for @testing-library/ React. You can look it up yourself.

There may be some future articles about testing. Such as:

How to test React hooks?

How do I test react routes?

How do I test the interface?

I hope this article was helpful to you, and you can also refer to my previous articles or share your thoughts and insights in the comments section.