Skip to content

Investigate react testing library as Enzyme replacement

Andrew Price edited this page Apr 4, 2019 · 9 revisions

React Testing Library (RTL)

https://github.com/kentcdodds/react-testing-library

The react-testing-library is a very lightweight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils, in a way that encourages better testing practices. Its primary guiding principle is:

The more your tests resemble the way your software is used, the more confidence they can give you.

Is it worth replacing Enzyme with RTL? What are the pros and cons?

Enzyme RTL
Uses shallow & deep rendering.

Shallow rendering faster than mounting

Doesn’t use shallow rendering.

Case against using shallow rendering by RTL author
https://kentcdodds.com/blog/why-i-never-use-shallow-rendering
"With shallow rendering, I can refactor my component's implementation and my tests break. With shallow rendering, I can break my application and my tests say everything's still working."

e.g. wrapper.instance().toggle() tests toggle function but not whether its called from click event.

Behind new React features
e.g. getDerivedStateFromError in errorBoundary
In sync with new features
Simulate events
e.g. wrapper.find('[data-test="btn-add-question-page"]').simulate("click");
Click events
e.g. fireEvent.click(buttonEl)
Instance of React components and snapshot Works with actual DOM nodes which can be queried
Find components in render or uses [data-testid] selector
e.g. wrapper.find(NavigationSidebar)
or wrapper.find('[data-test="side-nav"]')
Tries to more closely resemble how a user would use page.
e.g. getByLabelText to find form label
or getByText to find element with text.
Can also use [date-testid] as fallback alternative

Things that react-testing-library cannot do (out of the box):

  1. shallow rendering
  2. Static rendering (like enzyme's render function).
  3. Pretty much most of enzyme's methods to query elements (like find) which include the ability to find by a component class or even its displayName (again, the user does not care what your component is called and neither should your test). Note: react-testing-library supports querying for elements in ways that encourage accessibility in your components and more maintainable tests.
  4. Getting a component instance (like enzyme's instance)
  5. Getting and setting a component's props (props())
  6. Getting and setting a component's state (state())

All of these things are things which users of your component cannot do, so your tests shouldn't do them either.

RTL example

import React from 'react'
import {render, Simulate, wait} from 'react-testing-library'
// this adds custom expect matchers
import 'react-testing-library/extend-expect'
// the mock lives in a __mocks__ directory
import axiosMock from 'axios'
import GreetingFetcher from '../greeting-fetcher'

test('displays greeting when clicking Load Greeting', async () => {
  // Arrange
  axiosMock.get.mockImplementationOnce(({name}) =>
    Promise.resolve({
      data: {greeting: `Hello ${name}`},
    }),
  )
  const {getByLabelText, getByText, getByTestId, container} = render(
    <GreetingFetcher />,
  )

  // Act
  getByLabelText('name').value = 'Mary'

  Simulate.click(getByText('Load Greeting'))

  // let's wait for our mocked `get` request promise to resolve
  // wait will wait until the callback doesn't throw an error
  await wait(() => getByTestId('greeting-text'))

  // Assert
  expect(axiosMock.get).toHaveBeenCalledTimes(1)
  expect(axiosMock.get).toHaveBeenCalledWith(url)
  // here's a custom matcher!
  expect(getByTestId('greeting-text')).toHaveTextContent('Hello Mary')
  // snapshots work great with regular DOM nodes!
  expect(container.firstChild).toMatchSnapshot()
})
Clone this wiki locally