React Code Examples

For “Enjoying Mocks Responsibly”

Our ‘UsersPage’ hypothetical is back for a third time. As with the mockist example, we’re approaching it from the page component directly.

This time, however, child components are not being mocked out. In this approach those child components are merely implementation details of the page component, which is ultimately our singular unit under test.

Notice how the transport layer is stubbed out via the page component’s props to represent the different contexts that we want to simulate. Also notice how all the assertions are written exclusively from the client’s point of view.

import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { when } from 'jest-when';
import React from 'react';

import { Props, UsersPage } from './index';

const fetchStub: Props['fetch'] = () => new Promise(() => undefined);
const deleteStub: Props['delete'] = () => new Promise(() => undefined);

describe('when there are users', () => {
  it('displays the name and age of each user', () => {
    render(
      <UsersPage
        delete={deleteStub}
        fetch={fetchStub}
        users={[
          { name: 'Joe', age: 24, id: 'some-id-for-joe' },
          { name: 'Claire', age: 36, id: 'some-id-for-claire' },
        ]}
      />,
    );

    expect(screen.getByRole('cell', { name: 'Joe' })).toBeInTheDocument();
    expect(screen.getByRole('cell', { name: '24' })).toBeInTheDocument();

    expect(screen.getByRole('cell', { name: 'Claire' })).toBeInTheDocument();
    expect(screen.getByRole('cell', { name: '36' })).toBeInTheDocument();
  });

  describe('when deleting a user is pending', () => {
    it('displays a progress indicator', async () => {
      render(
        <UsersPage
          delete={deleteStub}
          fetch={fetchStub}
          users={[{ name: 'Joe', age: 24, id: 'some-id-for-joe' }]}
        />,
      );

      await userEvent.click(screen.getByRole('button', { name: 'Delete Joe' }));

      expect(await screen.findByRole('progressbar', { name: 'Deleting...' })).toBeInTheDocument();
    });
  });

  describe('when deleting a user succeeds', () => {
    beforeEach(() => {
      when(deleteStub).calledWith('some-id-for-joe').mockResolvedValue(undefined);
    });

    describe('when refetching users succeeds', () => {
      beforeEach(() => {
        when(fetchStub).mockResolvedValue([{ name: 'Claire', age: 36, id: 'some-id-for-claire' }]);
      });

      it('removes the user from the list', async () => {
        render(
          <UsersPage
            delete={deleteStub}
            fetch={fetchStub}
            users={[
              { name: 'Joe', age: 24, id: 'some-id-for-joe' },
              { name: 'Claire', age: 36, id: 'some-id-for-claire' },
            ]}
          />,
        );

        await userEvent.click(screen.getByRole('button', { name: 'Delete Joe' }));

        await waitForElementToBeRemoved(() =>
          screen.getByRole('progressbar', { name: 'Deleting...' }),
        );

        expect(screen.queryByRole('cell', { name: 'Joe' })).not.toBeInTheDocument();
      });
    });
  });

  describe('when deleting a user fails', () => {
    beforeEach(() => {
      when(deleteStub).calledWith('some-id-for-joe').mockRejectedValue(undefined);
    });

    it('displays an error alert', async () => {
      render(
        <UsersPage
          delete={deleteStub}
          fetch={fetchStub}
          users={[
            { name: 'Joe', age: 24, id: 'some-id-for-joe' },
            { name: 'Claire', age: 36, id: 'some-id-for-claire' },
          ]}
        />,
      );

      await userEvent.click(screen.getByRole('button', { name: 'Delete Joe' }));

      await waitForElementToBeRemoved(() =>
        screen.getByRole('progressbar', { name: 'Deleting...' }),
      );

      expect(
        await screen.findByRole('alert', { name: 'Deleting user "Joe" failed, please try again' }),
      ).toBeInTheDocument();
    });
  });
});

describe('when there are no users', () => {
  it('displays a message explaining why', () => {
    render(<UsersPage delete={deleteStub} fetch={fetchStub} users={[]} />);

    expect(screen.getByText('No users have signed up for you services.')).toBeInTheDocument();
  });
});

Back to the main article