Testing third-party React Native components using Jest and React Native Testing Library can be really frustrating. I remember tryin every event I saw in the docs to no avail. press
, click
, scroll
, the whole nine yards.
However, through blood, sweat and tears trial and error I managed to figure out a general approach that works and I wanted to share it to save others the stress.
I'm going to illustrate the approach by demonstrating simple tests on a component from the react-native-picker-select
package.
Locating the component in the tree
The first step for most tests is to locate the component(s) that will be involved. The problem is that not every third-party component provides the option to directly set a prop such as testID
, so I had to get creative.
We start by wrapping the component within a pair of <View>
tags and setting a prop on the parent <View>
, such as testID
. Afterwards, we can locate the component by accessing the <View>
's children. Here's some sample code to demonstrate:
//CustomPickerSelect.jsx
import React from 'react';
import { View } from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
const CustomPickerSelect = () => {
return (
<View testID='picker-select-parent'>
<RNPickerSelect
onValueChange={() => { }}
items={[]}
/>
</View>
)
}
export default CustomPickerSelect;
//CustomPickerSelect.test.js
import { render } from '@testing-library/react-native';
import CustomPickerSelect from './CustomPickerSelect';
test('Testing CustomPickerSelect', () => {
const { getByTestId } = render(<CustomPickerSelect />)
const picker = getByTestId("picker-select-parent").children[0];
})
picker
is the ReactTestInstance
that represents the third-party component RNPickerSelect
in the rendered tree.
Firing events
For third-party components, the names of the events (or event handlers) which elicit a response are usually listed in their props. The props are usually available in the documentation, but this is not always the case. The documentation may also be outdated, so it helps to see for yourself which props are available by logging the .props
property of the ReactTestInstance
. Let's do this now:
import { render } from '@testing-library/react-native';
import CustomPickerSelect from './CustomPickerSelect';
test('Testing CustomPickerSelect', () => {
const { getByTestId } = render(<CustomPickerSelect />)
const picker = getByTestId("picker-select-parent").children[0];
console.log("picker props: ", picker.props)
})
Here's a screenshot of the log:
Let's take a brief detour to discuss the main tool that we'll be using: fireEvent
. The fireEvent
method provided by React Native Testing Library has the following syntax:
fireEvent(element: ReactTestInstance, eventName: string, ...data: Array<any>): void
If the meaning of the data
parameter is unclear to you, it actually represents the argument(s) that will be passed to the event handler that is being invoked.
The challenge is determining the right value to use for the eventName
argument. This is where we reference the props for the ReactTestInstance
that were logged earlier. The event handlers are usually prefixed by "on-", for example onPress
.
Here are the event handlers for our ReactTestInstance
:
Let's make a few additions to the CustomPickerSelect.jsx
component to facilitate the testing of the onValueChange
event handler:
import React, { useState } from 'react';
import { View } from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
const CustomPickerSelect = () => {
const [value, setValue] = useState("")
return (
<View testID='picker-select-parent'>
<RNPickerSelect
value={value}
onValueChange={(newValue) => { setValue(newValue) }}
items={[]}
/>
</View>
)
}
export default CustomPickerSelect;
Now let's invoke the onValueChange
event handler and check to make sure that everything works as expected:
import { render, fireEvent } from '@testing-library/react-native';
import CustomPickerSelect from './CustomPickerSelect';
test('Testing CustomPickerSelect', () => {
const { getByTestId } = render(<CustomPickerSelect />)
const picker = getByTestId("picker-select-parent").children[0];
fireEvent(picker, 'onValueChange', "A");
expect(picker.props.value).toBe("A");
})
It's generally not encouraged to assert on implementation details, but it can't hurt to know how to do this ;).
If you want to conduct other tests, you can make the necessary changes. For example, here's some code to test if a callback function passed in as a prop to the CustomPickerSelect
component is invoked:
import React, { useState } from 'react';
import { View } from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
const CustomPickerSelect = ({ setValueCallback }) => {
const [value, setValue] = useState("")
return (
<View testID='picker-select-parent'>
<RNPickerSelect
value={value}
onValueChange={(newValue) => {
setValue(newValue);
setValueCallback(newValue);
}}
items={[]}
/>
</View>
)
}
export default CustomPickerSelect;
import { render, fireEvent } from '@testing-library/react-native';
import CustomPickerSelect from './CustomPickerSelect';
test('Testing CustomPickerSelect', () => {
const mockCallback = jest.fn();
const { getByTestId } = render(
<CustomPickerSelect
setValueCallback={mockCallback}
/>
)
const picker = getByTestId("picker-select-parent").children[0];
fireEvent(picker, 'onValueChange', "A");
expect(mockCallback).toHaveBeenCalled();
})
That's it for now. Thanks for reading and share this with a friend if this helped.
If you liked this article, then please support me
Sources
https://www.npmjs.com/package/react-native-picker-select
https://callstack.github.io/react-native-testing-library/docs/api#fireevent
Top comments (0)