id | title |
---|---|
faq |
FAQ |
See also the main FAQ for questions not specific to React testing
How do I test input onChange handlers?
TL;DR:
Go to the on-change.js
example
In summary:
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import { render, fireEvent } from 'react-testing-library'
test('change values via the fireEvent.change method', () => {
const handleChange = jest.fn()
const { container } = render(<input type="text" onChange={handleChange} />)
const input = container.firstChild
fireEvent.change(input, { target: { value: 'a' } })
expect(handleChange).toHaveBeenCalledTimes(1)
expect(input.value).toBe('a')
})
test('checkboxes (and radios) must use fireEvent.click', () => {
const handleChange = jest.fn()
const { container } = render(
<input type="checkbox" onChange={handleChange} />
)
const checkbox = container.firstChild
fireEvent.click(checkbox)
expect(handleChange).toHaveBeenCalledTimes(1)
expect(checkbox.checked).toBe(true)
})
If you've used enzyme or React's TestUtils, you may be accustomed to changing inputs like so:
input.value = 'a'
Simulate.change(input)
We can't do this with react-testing-library because React actually keeps track
of any time you assign the value
property on an input
and so when you fire
the change
event, React thinks that the value hasn't actually been changed.
This works for Simulate because they use internal APIs to fire special simulated events. With react-testing-library, we try to avoid implementation details to make your tests more resiliant.
So we have it worked out for the change event handler to set the property for
you in a way that's not trackable by React. This is why you must pass the value
as part of the change
method call.
Can I write unit tests with this library?
Definitely yes! You can write unit and integration tests with this library. See below for more on how to mock dependencies (because this library intentionally does NOT support shallow rendering) if you want to unit test a high level component. The tests in this project show several examples of unit testing with this library.
As you write your tests, keep in mind:
The more your tests resemble the way your software is used, the more confidence they can give you. - 17 Feb 2018
If I can't use shallow rendering, how do I mock out components in tests?
In general, you should avoid mocking out components (see the Guiding Principles section). However if you need to, then it's pretty trivial using Jest's mocking feature. One case that I've found mocking to be especially useful is for animation libraries. I don't want my tests to wait for animations to end.
jest.mock('react-transition-group', () => {
const FakeTransition = jest.fn(({ children }) => children)
const FakeCSSTransition = jest.fn(props =>
props.in ? <FakeTransition>{props.children}</FakeTransition> : null
)
return { CSSTransition: FakeCSSTransition, Transition: FakeTransition }
})
test('you can mock things with jest.mock', () => {
const { getByTestId, queryByTestId } = render(
<HiddenMessage initialShow={true} />
)
expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists
// hide the message
fireEvent.click(getByTestId('toggle-message'))
// in the real world, the CSSTransition component would take some time
// before finishing the animation which would actually hide the message.
// So we've mocked it out for our tests to make it happen instantly
expect(queryByTestId('hidden-message')).toBeNull() // we just care it doesn't exist
})
Note that because they're Jest mock functions (jest.fn()
), you could also make
assertions on those as well if you wanted.
Open full test for the full example.
This looks like more work that shallow rendering (and it is), but it gives you more confidence so long as your mock resembles the thing you're mocking closely enough.
If you want to make things more like shallow rendering, then you could do something more [like this](Open full test ).
Learn more about how Jest mocks work from my blog post: "But really, what is a JavaScript mock?"
What about enzyme is "bloated with complexity and features" and "encourage poor testing practices"?
Most of the damaging features have to do with encouraging testing implementation details. Primarily, these are shallow rendering, APIs which allow selecting rendered elements by component constructors, and APIs which allow you to get and interact with component instances (and their state/properties) (most of enzyme's wrapper APIs allow this).
The guiding principle for this library is:
The more your tests resemble the way your software is used, the more confidence they can give you. - 17 Feb 2018
Because users can't directly interact with your app's component instances, assert on their internal state or what components they render, or call their internal methods, doing those things in your tests reduce the confidence they're able to give you.
That's not to say that there's never a use case for doing those things, so they should be possible to accomplish, just not the default and natural way to test react components.
Why isn't snapshot diffing working?
If you use the snapshot-diff library to save snapshot diffs, it won't work out of the box because this library uses the DOM which is mutable. Changes don't return new objects so snapshot-diff will think it's the same object and avoid diffing it.
Luckily there's an easy way to make it work: clone the DOM when passing it into snapshot-diff. It looks like this:
const firstVersion = container.cloneNode(true)
// Do some changes
snapshotDiff(firstVersion, container.cloneNode(true))
Does this library work with React Native?
This is still quite experimental - please contribute with your own results/findings!
The short answer is yes, but with a few caveats. It's possible to replicate a
lot of DOM functionality with
react-native-web
, allowing you
to use the query APIs like getByText
. You can then add a press
event to
fireEvent
that simulates a mouseDown immediately followed by a mouseUp, and
call this with Touchable* components.
One thing this approach does not support is any kind of native module functionality (like native navigation modules). The way around this is to design your components so that as much of the functionality you need tested is encapsulated outside of any native module functionality.
For a barebones example of testing a React Native component, see here.
There is also a sibling project called react-native-testing-library which aims to test React Native apps without mentioned tradeoffs, having the API inspired by and mostly compatible with this library.