Tools to help to test: https://testing-playground.com/

Introduction

I will just replicate here what we found on @testing-library home page because it's already a very awesome summary where I share the same thoughts:

Complete and straightforward testing utilities that encourage good testing practices. The more your tests resemble the way your software is used, the more confidence they can give you.

What are the straightforward points of the library proposal: Write maintainable code: Tests only break when your app breaks, not implementation details. Develop with Confidence: Interact with your app the same way as your users. Accessible by Default: Built-in selectors find elements the way users do to help you write inclusive code.

The problem (code from: https://blog.logrocket.com/enzyme-vs-react-testing-library-a-mindset-shift/):

describe("RangeCounterA", () => {
  let component;
  beforeEach(() => {
    component = mount(<RangeCounterA />);
  });

  describe("when incrementing counter is allowed", () => {
    it("updates counter value correctly", () => {
      component.instance().incrementCounter();
      expect(component.state().counter).toEqual(1);
      expect(component.state().hasEdited).toEqual(true);
    });
  });
});

With @testing-library:

describe("RangeCounterB", () => {
  describe("when incrementing counter is allowed", () => {
    it("updates the counter value", async () => {
      const { getByTestId, getByText } = render(<RangeCounterB min={2} />);
      const incrementButton = getByText("+");
      fireEvent.click(incrementButton);
      expect(getByTestId("counter-value").innerHTML).toEqual("3");
    });
  });
});

But we can do something similar with Enzyme, testing the result not the implementation following testing-library philosophy of testing; in the following code first, you will see how to test with Enzyme following testing-library principles, then you will see the same test, but with testing-library:

// components/Calendar/__tests__/Calendar.test.tsx

describe('<Calendar />  with Enzyme', () => {
  const setup = makeSetupComponent({ component: Calendar });

  test('should have integration with the toolbar', () => {
    const { component } = setup();

    const toolbar = component.find('[data-testid="toolbar"]');
    expect(toolbar.children()).toHaveLength(9);
    const toolbarText = toolbar.text();
    expect(toolbarText).toContain(moment(Date.now()).format(DATE_FORMATS.TOOLBAR_LABEL));
    expect(component.find('p').at(0).text()).toContain('Current view: week');

    const toolbarNextButton = toolbar.find('button').at(2);
    expect(toolbarNextButton.text()).toContain('Next');
    toolbarNextButton.simulate('click');

    const toolbarMonthButton = toolbar.find('button').at(5);
    expect(toolbarMonthButton.text()).toContain('month');
    toolbarMonthButton.simulate('click');

    const updatedToolbarText = toolbar.at(0).text();
    const expectedToolbarDate = moment(new Date(2021, 3, 19)).format(DATE_FORMATS.TOOLBAR_LABEL);
    expect(updatedToolbarText).toContain(expectedToolbarDate);
    expect(component.find('p').at(0).text()).toContain('Current view: month');
  });

  test('should have integration with DateCellWrapper', () => {
    const { component } = setup();

    const toolbarMonthButton = component.find('[data-testid="toolbar"]').find('button').at(5);
    expect(toolbarMonthButton.text()).toContain('month');
    toolbarMonthButton.simulate('click');

    component.find('[data-testid="dataCellWrapper-button"]').at(4).simulate('click');
    const sideColumnAgenda = component.find('.rbc-agenda-table').at(1).text();
    expect(sideColumnAgenda).toContain('All Day Event very long title');
  });
});

describe('<Calendar /> with @testing-library', () => {
  test('should have integration with the toolbar', () => {
    render(<Calendar />);

    expect(screen.queryByText(moment(Date.now()).format(DATE_FORMATS.TOOLBAR_LABEL))).toBeTruthy();
    expect(screen.queryByText('Current view: week')).toBeTruthy();

    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    fireEvent.click(screen.getByRole('button', { name: 'month' }));

    expect(screen.queryByText('Current view: month')).toBeTruthy();
    expect(screen.getAllByTestId('toolbar')).toHaveLength(2);
  });

  test('should have integration with DateCellWrapper', () => {
    const { container } = render(<Calendar />);
    fireEvent.click(screen.getByRole('button', { name: 'month' }));
    fireEvent.click(screen.getAllByTestId('dataCellWrapper-button')[4]);
    expect(container.getElementsByClassName('rbc-agenda-event-cell')[0].textContent).toContain(
      'All Day Event very long title',
    );
  });

  test('Renders modal when clicking calendar event', () => {
    // solution to make typescript understand that this is a mock and avoid the error
    // mockImplementationOnce does not exist on type useInteractiveClasses
    const useInteractiveClassesMock = useInteractiveClasses as jest.Mock<
      IInteractiveClassesProviderValue
    >;
    useInteractiveClassesMock.mockImplementationOnce(() => ({
      classInstances: [
        {
          ...mockClassInstances(new Date())[0],
          start: new Date('2021-04-19'),
          end: new Date('2021-04-19'),
          title: 'All Day Event very long title',
          subCategory: '',
          trainerName: '',
        },
      ],
    }));

    render(<Calendar />);
    expect(screen.queryByText('All Day Event very long title')).toBeTruthy();
    fireEvent.click(screen.getByText('All Day Event very long title'));
    expect(screen.queryByText('Edit')).toBeTruthy();
    fireEvent.click(screen.getByText('Edit'));
    expect(screen.queryByText('Cancel')).toBeTruthy();
    fireEvent.click(screen.getByText('Cancel'));
  });
});

How to test forms with testing-library

Independently of the form's library that you're using, this is the way of testing following testing-library principles:

import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import { MyForm } from './myForm.js'

test('rendering and submitting a basic Formik form', async () => {
  const handleSubmit = jest.fn()
  render(<MyForm onSubmit={handleSubmit} />)

  userEvent.type(screen.getByLabelText(/first name/i), 'John')
  userEvent.type(screen.getByLabelText(/last name/i), 'Dee')
  userEvent.type(screen.getByLabelText(/email/i), '[email protected]')

  userEvent.click(screen.getByRole('button', { name: /submit/i }))

  await waitFor(() =>
    expect(handleSubmit).toHaveBeenCalledWith({
      email: '[email protected]',
      firstName: 'John',
      lastName: 'Dee',
    }, expect.anything())
  )
})

Custom renders

On testing-library you supposed to create a custom render for every Wrapper you want to create; for that, you have two ways. The first way is:

function Wrapper({ children }) {
    return <IntlProvider locale={locale}>{children}</IntlProvider>
}

function customRender(ui, options) {
    return render(<Wrapper>{ui}</Wrapper>, options })
}

The second way is:

function Wrapper({ children }) {
    return <IntlProvider locale={locale}>{children}</IntlProvider>
}

function customRender(ui, options) {
    return render(ui, { wrapper: Wrapper, ...options } })
}

I do I prefer the first way? It's because if you use the wrapper API in options on your test, you'll still be able to do it;