Unit and Integration Testing Components

Learning Goals

  • Review differences between unit, integration, and acceptance testing
  • Know how to setup a testing suite from scratch within create-react-app
  • Identify when mocking is needed and how to mock in our apps
  • Go over some of the different ways of testing including:
    • Test the display of a component determined by props
    • Test results of user interactions
    • Test class methods using Jest
  • This lesson does not cover how to deal with asynchronous JS (like fetch)

Why Do We Test? What Should We Test?

Journal on Your Own (3 minutes)

  1. Why do we test? What is the point of writing tests?
  2. What have you been testing in apps so far?

When testing React apps, or really when testing any kind of application, most of the tests your write will be unit and integration tests.

Your Turn

We hear a lot about unit, integration, and acceptance tests. What is the difference between the three of these? Take 5 minutes and see what you and your partner can find out.

Tools to Test

To test any React application, and more broadly almost any application, we need three tools to help us:

  • A test runner
  • Something to “render” components for us and execute that component code
  • An assertion library

Your Turn

What test runners and assertion libraries have you used in the past, and what was each library doing for you?

For our React apps, we’re going to use:

Bookmark these links! You’ll be going to these documentation pages a lot this mod and throughout your new dev career, especially if you’re writing code using React. It takes practice to read through docs, and now is a great time to get more practice.

Setup of Tools

One great thing about create-react-app is that it comes with Jest installed. Jest was created by Facebook just as React was, so it makes sense that it is included in create-react-app by default. So we don’t have to install it.

We can double-check that it’s installed by looking into the node_modules directory and finding a directory within it called jest. If you see that jest directory, then you can be assured that it’s already installed.

However, React Testing Library is not given to us by default with create-react-app. So we need to install this ourselves.

Your Turn

Head on over to the React Testing Library docs and see if you can find how to install this library.

Installation Command

Run the command in your terminal: npm install --save-dev @testing-library/react

Unit Testing

What is the goal of a unit test? It depends on what we’re working with. If we want to test a bunch of functions, then each test can test the functionality of each function. If we are dealing with components, then we can have a test where only one component is rendered, and the component is treated as a unit.

Our goals with component unit tests will be to:

  • Render the component
  • Check that everything we expect to see on the page is there
  • Interact with the component as a user would and check that the app responds as we expect

Let’s take this step by step and dive into the docs.

Rendering Components

Let’s Do Some Research

Head on over to the React Testing Library docs and see if you can find how to render components within a test.

Rendering a Component

This example page is a good resource as well as the specific API page.

At the top of the component test file we need to import { render } from '@testing-library/react'.

Then use render(<ComponentUnderTest />) to render the component that we are interested in testing.

Finding Elements on the Page

React Testing Library comes with another library already installed with it. It’s called the DOM Testing Library. The DOM Testing Library comes with some really nice tools to find elements on the page.

Back to the Docs

Using the DOM Testing Library docs, see if you can find a way to:

  • Obtain all the elements that have the text “Idea!” within them
  • Obtain an input with the placeholder text “Enter name here…”

If you finish this, then look into the different between getAllBy queries and queryAllBy queries.

Finding Elements

This Queries API docs page is a going to be your best friend to help you find how to get the element(s) you’re looking for.

The good thing about using this library to find elements is that it really pushes us as developers to make our apps accessible. So many of the queries available to us in the library are based around accessibility.

Interacting with Elements

Lastly, we need to be able to interact with the page/component, like click a button or enter text into an input field. How do we do this?

One More Time!

What can you find in the DOM Testing Library docs that could be used to click a button or put text into an input field?

Interaction Events

This firing events part of the DOM Testing Library docs is a going to be your best friend to help you fire events like button clicks.

The React Testing Library and DOM Testing Library docs are pretty nice once you get used to them. Right now, they might seem big and verbose, but you will get to know them the more you practice reading through them to help you figure out testing.

Keep in mind that by installing React Testing Library, we get the functionality of the DOM Testing library for free!

Organize the Library Chaos

Make a table in your notebook or on a piece of paper with the following columns:

  • Library Name
  • Installation Command
  • What is it? Why would we use it?

Testing What is Rendering to the Page

Let’s bring all of our research in the docs together to make our first test! Use this repo as a starting point - it’s an ideabox that has a form and delete button already implemented. Make sure you clone this down outside of any other React projects you have right now. Here are the steps to set it up:

git clone git@github.com:turingschool-examples/ideabox-testing-rtl.git
cd ideabox-testing-rtl
npm install
npm start

Test out the app in the browser. Add some ideas. Delete some ideas. Then explore the codebase, looking at each component one-by-one to see how things are connected.

Set Up the Test

  1. Install the React Testing Library
  2. Create a test file for the Card component
  3. Create a describe block for the Card component and then an it block for testing that the card displays information on the page
  4. Within the it block, verify your test is setup correctly so far by expecting true to be equal to true. Then switch it to test that true is equal to false to make sure it fails.

Now that we know our Jest tests can run and our assertions work, we can move on to testing that the Card is rendering what we expect.

Render the Component

  1. Remove the assertion of true to be equal to true
  2. Render the Card component in the it block (making sure to pass in props that the component needs)
  3. Find the element with a certain title text (you decide what the title text should be via the props)

For the removeIdea prop, what do you pass in??? For now, pass in a Jest function: removeIdea={jest.fn()}. We’ll talk more about this in later testing lessons.

Next we need to make an assertion. You found the element on the page, but you want to assert that it is generally on the page. For that, there is a library to help called jest-dom.

Make Assertions

  1. Install the jest-dom library
  2. Import the library into your test file using import '@testing-library/jest-dom';
  3. Head to the Custom Matchers section of the jest-dom docs to find how to assert that something is in the document
  4. Repeat the assertion for the Card’s description and delete button

A Finished Test

// Card.test.js

import React from 'react';
import Card from './Card';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';

describe('Card', () => {
  it('displays correct information in the Card', () => {
    const { getByText } = render(<Card
                                  id={1}
                                  title="New Idea"
                                  description="Something new I thought of"
                                  removeIdea={jest.fn()}
                                  />);
    expect(getByText("New Idea")).toBeInTheDocument();
    expect(getByText("Something new I thought of")).toBeInTheDocument();
    expect(getByText("Delete")).toBeInTheDocument();
  });
});

Debugging

This section of the testing API page shows how you can see what is rendered in the test, which can help you debug tests when things go wrong.

Interactive Unit Testing

The Card component has a button that when clicked, fires a function that was passed down through the component via props. We want to be able to test that when that button is clicked, the function is fired with the correct argument(s).

To do that, we need to know more about the jest.fn() we used earlier.

A jest.fn() is known as a “mock”. Here is the documentation on Jest mock functions. By using a jest.fn() function, it gives us the ability to:

  • Provide a mock function to override the original function’s behavior
  • Track that the mock function was called and was called with certain arguments, if applicable

Our goal is to:

  • Give the component a mock function that we can reference later (to know that it was called)
  • Trigger the event that would call the function in the component
  • Check that the mock function was called and check if it was called with the correct arguments, if applicable

Let’s setup this test:

it('passes the correct id to the removeIdea function', () => {
    const mockRemoveIdea = jest.fn();
    const { getByText } = render(<Card
            id={1}
            title="New Idea"
            description="Something new I thought of"
            removeIdea={mockRemoveIdea}
            />);

    // Execute the code by clicking the button

    // Assert that the mockRemoveIdea was called with the correct argument
  })
});

Finish the Test

  1. Find out how to trigger the function by clicking the delete button
  2. Assert that the mocked function was called with the correct argument

Hint: assertions/expectations/matchers for mocked functions can be found here along with other Jest matchers.

Checking a Mocked Function

it('passes the correct id to the removeIdea function', () => {
    const mockRemoveIdea = jest.fn();
    const { getByText } = render(<Card
            id={1}
            title="New Idea"
            description="Something new I thought of"
            removeIdea={mockRemoveIdea}
            />);

    fireEvent.click(getByText("Delete"));
    expect(mockRemoveIdea).toHaveBeenCalledWith(1);
  })
});

Congrats! You wrote a great unit test for this component. It gives us assurance that in the chain of event that occurs when the Delete button is clicked, that this is performing as expected.

Overwriting Existing Functionality with Mocks

Let’s look at the Form component’s functionality. Using the logic from clicking the delete button in the Card unit test, we should be able to apply the same logic to triggering the addIdea function within the Form and test that it was called with the correct argument(s).

Write the Test

  1. Create a new test file for the Form component
  2. Write a new it block for this test
  3. Setup your test file so that you can render the Form component using React Testing Library
  4. Pass in a mocked function for the addIdea prop
  5. Fill in the input fields and click the submit button
  6. Check that the mocked addIdea function was called with the correct argument

Use debug() along the way to see the progress of your components.

Mocking the addIdea Function

// Form.test.js

import React from 'react';
import Form from './Form';
import { render, fireEvent } from '@testing-library/react';

describe('Form', () => {
  it('sends the correct data up to app via addIdea', () => {
    const mockAddIdea = jest.fn();

    const {getByPlaceholderText, getByText} = render(<Form addIdea={mockAddIdea} />);

    fireEvent.change(getByPlaceholderText('title'), {target: {value: 'Best!'}});
    fireEvent.change(getByPlaceholderText('description'), {target: {value: 'Idea!'}});
    fireEvent.click(getByText('Submit!'));

    expect(mockAddIdea).toHaveBeenCalledWith({id: Date.now(), title: "Best!", description: "Idea!"});
  });
});

But the test is failing! Why??

We need to overwrite the functionality of Date.now() to get a consistent value when we run our tests. We can do that with Jest mock functions too! Within the it block:

it('sends the correct data up to app via addIdea', () => {
  const mockAddIdea = jest.fn();
  Date.now = jest.fn().mockImplementation(() => 1584585306565);

  const {getByPlaceholderText, getByText} = render(<Form addIdea={mockAddIdea} />);

  fireEvent.change(getByPlaceholderText('title'), {target: {value: 'Best!'}});
  fireEvent.change(getByPlaceholderText('description'), {target: {value: 'Idea!'}});
  fireEvent.click(getByText('Submit!'));

  expect(mockAddIdea).toHaveBeenCalledWith({id: 1584585306565, title: "Best!", description: "Idea!"});
});

We can reassign the Date.now function to be a function that we control using Jest’s mockImplementation. In essence, we are overwriting Date.now() to return the value 1584585306565 every time it is called in this test (but not for our actual production code).

Integration Testing

Integration testing is all about testing multiple functions or components working together. In our case, a great integration test would be to test that we can submit a new idea to the page.

To begin an integration test, ask yourself, “What components need to be rendered to perform this task?”

In this case, the entire application needs to be rendered because we need the form and we need to see that the Card shows up on the page. So let’s make an integration test for App.

Write the Integration Test

  1. Go into the App.test.js file or create a new test file for App if one does not exist
  2. Write a new it block for this test
  3. Setup your test file so that you can render the App component using React Testing Library
  4. Fill in the input fields and click the submit button
  5. After submit, check that the inputs have been cleared
  6. Check that the new idea has been added to the page

Use debug() along the way to see the progress of your components.

Adding an Idea Integration Test

// App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
  ReactDOM.unmountComponentAtNode(div);
});

describe('App', () => {
  it('can add a new idea', () => {
    const {getByPlaceholderText, getByText} = render(<App />);

    fireEvent.change(getByPlaceholderText('title'), {target: {value: 'Best!'}});
    fireEvent.change(getByPlaceholderText('description'), {target: {value: 'Idea!'}});
    fireEvent.click(getByText('Submit!'));

    expect(getByPlaceholderText('title').value).toEqual('');
    expect(getByPlaceholderText('description').value).toEqual('');

    expect(getByText("Best!")).toBeInTheDocument();
    expect(getByText("Idea!")).toBeInTheDocument();
  });
});

Some More Tests to Write

  1. Unit: In the Form, test that it displays correctly
  2. Unit: In the Form, check that when you type in an input that the correct value displays in the input
  3. Integration: Test that you can delete an idea

Vocab

  • Unit test - Unit tests test the smallest unit of functionality, typically a method/function. For example, when you call a specific method on a class, you would likely expect a value to be returned. Unit tests should be focused on one particular feature
  • Integration test - Integration tests build on unit tests by combining the units of code and testing that the resulting combination functions correctly
  • Acceptance test - (also known as end-to-end tests) Once an application is ready to use, it undergoes testing in a browser by a user (or automated user) to verify that the features behave normally and meet expectations
  • Jest - Jest is a JS framework created by Facebook that is included in React and acts as a test runner, assertion library, and mocking library. It can also be used for snapshot testing.
  • React Testing Library -
  • Mock - Mocks are created in order to replicate the data or functions you would expect to have or be fired, or when we want to set up a function that runs in place of another function

Extra Resources:

Lesson Search Results

Showing top 10 results