Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enzyme vs React Testing Library #3

Open
einatnielsen opened this issue Dec 2, 2019 · 7 comments
Open

Enzyme vs React Testing Library #3

einatnielsen opened this issue Dec 2, 2019 · 7 comments

Comments

@einatnielsen
Copy link
Contributor

The purpose of this issue is to review the pros and cons between Enzyme and React Testing Library.

  • For the purpose of this discussion we will refer to React Testing Library as RTL.

RTL Pros:

  1. RTL is built on top of react-dom and react-dom/test-utils, thus supports new React features out of the box
  2. RTL approach is testing user behaviour which leads you to be more confident in your tests
  3. RTL limits the way you can interact with elements, and thus forces you to write code that is a11y compliant (best practice).
  4. FB recommend using RTL

Enzyme Cons:

  1. Enzyme is built on top of a custom 'react-dom', thus supporting React new features will not be out of the box
  2. Enzyme unit testing makes you lean towards testing implementation details (state and props) which your user does not care.
  3. Enzyme encourage using ID and css selector, by that your tests are likely to break when you refactor.
@einatnielsen
Copy link
Contributor Author

I also found this tool called Spearmint: a tool to create RTL tests from UI, I tested it but I am not sure it is good enough:
https://www.spearmintjs.com/

@MatanBorenkraoutNielsen
Copy link
Contributor

One more thing I encountered:
Testing components with async componentDidMount come in pretty complicated when trying to test them in enzyme:
enzymejs/enzyme#1587

RTL unlike Enzyme, waits and retries to get element until it is available therefor we can test async componentDidMount in other ways like loader appears and disappears and so on (many other ways available).

Another thing is like @einatnielsen mentioned, enzyme uses enzyme-adapter that also creates some extra configuration and from time to time - updates.

@MatanBorenkraoutNielsen
Copy link
Contributor

Summary

CC: @lidoravitan @einatnielsen @liadShiranNielsen @thehulke @amitNielsen @MatanBorenkraoutNielsen

Issues:

  • We talked about RTL vs. Enzyme.
  • We decided to go on with RTL and work on examples to show how it's different from Enzyme, we need to build 3 examples: Container, Class component and Functional component.
  • We decided we like RTL's approach to test components like the users sees them, test behavior and not implementation details

With RTL:

function HiddenMessage({children}) {
  const [showMessage, setShowMessage] = React.useState(false)
  return (
    <div>
      <label htmlFor="toggle">Show Message</label>
      <input
        id="toggle"
        type="checkbox"
        onChange={e => setShowMessage(e.target.checked)}
        checked={showMessage}
      />
      {showMessage ? children : null}
    </div>
  )
}

test('shows the children when the checkbox is checked', () => {
  const testMessage = 'Test Message'
  const {queryByText, getByLabelText, getByText} = render(
    <HiddenMessage>{testMessage}</HiddenMessage>
  )

  expect(queryByText(testMessage)).toBeNull()
  fireEvent.click(getByLabelText(/show/i))
  expect(getByText(testMessage)).toBeInTheDocument()
})

With Enzyme:

function HiddenMessage({children}) {
  const [showMessage, setShowMessage] = React.useState(false)
  return (
    <div>
      <label htmlFor="toggle">Show Message</label>
      <input
        id="toggle"
        type="checkbox"
        class="my-checkbox-class"
        onChange={e => setShowMessage(e.target.checked)}
        checked={showMessage}
      />
      {showMessage ? <span class="my-children-class"> {children} </span> : null}
    </div>
  )
}

test('shows the children when the checkbox is checked', () => {
  const testMessage = 'Test Message'
  const wrapper = mount(
    <HiddenMessage>{testMessage}</HiddenMessage>
  )

  expect(wrapper.find('.my-children-class').text()).not.toBe('Test Message')

  const checkbox = wrapper.find('.my-checkbox-class')
  checkbox.simulate('change', { target: { checked: true } })

  expect(wrapper.find('.my-children-class').text()).toBe('Test Message')
})
  • RTL limits the way you can interact with elements, and drives you to meet best practices.
    The following is a list of selectors provided by RTL:
  1. getByLabelText - find by label or aria-label text content
  2. getByPlaceholderText
  3. getByText
  4. getByDisplayValue
  5. getByAltText
  6. getByTitle
  7. getByRole
  8. getByTestId

With RTL:

 expect(getByText('Test Message')).toBeInTheDocument()

With Enzyme:

  const element = wrapper.find('.my-class')
  expect(element.text()).toBe('Test Message')
  • Async componentDidMount - When having side effects inside componentDidMount, we want to test the DOM after the async componentDidMount finished.
    In order to do that in Enzyme we can:
    Flush all open promises by (Explained here):
await new Promise(resolve => setImmediate(resolve));

or just as seen in our Enzyme tests now:

mount(<MyComponent/>)
wait(randomNumberOfMs)

Waiting doesn't ensure us that what we wanted will happen, it's a matter of luck and our computer's CPU.

In RTL we can just:

import { render, wait } from "@testing-library/react";
const { getByText } = render(<MyComponent />);
await wait(() => expect(getByText("Mocked Data")).toBeInTheDocument());

@lidoravitan @einatnielsen - I didn't sum things up while in the meeting so if I missed something please let me know and I'll add it.

@lidoravitan
Copy link
Contributor

@einatnielsen @MatanBorenkraoutNielsen what are the cons of using RTL? what are the risks of switching from enzyme? (@talJoffeExelate )

@MatanBorenkraoutNielsen
Copy link
Contributor

At the moment we couldn't find the cons, as @thehulke mentioned, it looks like Enzyme was a step along the way and RTL actually expands its abilities and makes writing tests simpler and easier.
The only thing we saw for now is that Enzyme is widely adopted and RTL is being adopted at these moments. Enzyme weekly downloads on npm are about 1.7M and RTL has only 500K.
The main risk I see with switching is that we won't be able to rewrite all of our tests, and using both enzyme and RTL in our testbase might cause troubles with webapps-app-test-utils, looking forwards I believe this risk will pay out.

@MatanBorenkraoutNielsen
Copy link
Contributor

@lidoravitan @einatnielsen
While looking a bit more on RTL I found that they also have a cypress wrapper, it adds RTL functionality to cypress so selectors will work the same in Cypress as they will work when using RTL.

@MatanBorenkraoutNielsen
Copy link
Contributor

CRA(Create React App) version 3.3.0 added built in support for React-Testing-Library:
https://twitter.com/kentcdodds/status/1202591559272648705

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants