Testing React Native components in Node with react-test-renderer

Paul Sherman
4 min readMay 22, 2018

--

Background

The react-test-renderer package makes it convenient to test components outside of their native environment (e.g. on an iOS/Android device for React Native components). Instead of rendering “real” components, react-test-renderer renders JavaScript objects so that the tests can be run in Node.

Below, we will cover rendering elements, testing props, finding nested elements, updating props, and calling event handlers. This is by no means a definitive guide, but hopefully points you in the right direction.

Setup

npm install react-test-renderer --save-dev

If you are using Jest to run your tests, you will want to make sure to use the React Native preset. If your project was created using react-native init, this should already be configured (check your package.json or jest.config.js)

// package.json
{
"jest": {
"preset": "react-native"
}
}
// jest.config.js
module.exports = {
preset: "react-native",
// ...
};

Note: Snapshot testing is popular use case with the react-test-renderer. This article does not cover snapshots, but you can read about them in the Jest documentation.

Testing

The Component

Tests will be run for this <Testable> component.

// components/Testable.js
import React from 'react';
import { View, Text } from 'react-native';
import { Consumer } from './Context';
const Testable = props => (
<Consumer>
{value => (
<View>
<Text>{value} & {props.pair}</Text>
</View>
)}
</Consumer>
);
export default Testable;

<Testable> is a pretty boring component, but the big thing to note is the <Consumer> component. This comes from React’s new context API and is paired with a <Provider> component. These components are created by calling React.createContext().

// components/Context.js
import React from 'react';
const { Provider, Consumer } = React.createContext('test');
export { Provider, Consumer };

<Testable> renders a <Consumer>, which means that it needs to be rendered inside of a <Provider>.

// New context API reference
const App = () => (
<Provider value="red">
<Testable pair="blue" />
</Provider>
);
/* App will render:
* <View>
* <Text>Red & Blue</Text>
* </View>
*/

Rendering with react-test-renderer

The default export from react-test-renderer is an object with a create method. We can pass create a React element and it will return a renderer instance.

import React from 'react';
import 'react-native';
import renderer from 'react-test-renderer';
import { Provider } from '../src/components/Context';
import Testable from '../src/components/Testable';
describe('<Testable>', () => {
it('renders the correct text', () => {
const value = 'greetings';
const pair = 'salutations';
const inst = renderer.create(
<Provider value={value}>
<Testable pair={pair} />
</Provider>
);
});
});

The renderer instance has a few methods that we will cover later, but for now we will focus on the root property it exposes. root is an element instance for the root component that was rendered (<Provider> here). Element instances expose a few properties that are useful in testing: type, props, and the instance’s parent and children instances.

Finding Components

In addition to the above properties, element instances also provide methods that can be used to find descendants.

  1. instance.findByType is used to find an element instance with the provided component type.
  2. instance.findByProps is useful for finding an element instance with the provided props.
  3. There are also “all” versions of these functions (findAllByType and findAllByProps) that you can use to find multiple instances.

In the above test, we want to verify that the the correct text is rendered, so we should look for a <Text> component. We can then check that element instance’s props.children (not the children property of the instance, which is an element instance), to verify the output.

import { Text } from 'react-native';describe('<Testable>', () => {
it('renders the correct text', () => {
const value = 'greetings';
const pair = 'salutations';
const inst = renderer.create(
<Provider value={value}>
<Testable pair={pair} />
</Provider>
);
const textInst = inst.root.findByType(Text);
expect(
textInst.props.children.join()
).toBe(`${value} & ${pair}`);
});
});

We need to join textInst.props.children because the JSX:

<Text>{value} & {props.pair}</Text>

is equivalent to:

React.createElement(Text, null, value, " & ", props.pair)

so the children is an array, not a string.

Updating Props

If you want to re-render your elements, you can call the update method of the renderer instance. This should be passed the new React element tree to be rendered. As long as the type of the root element passed to update is the same as the one passed to create, the element instances will be updated instead of unmounted and replaced.

describe('<Testable>', () => {
it('updates with the correct text', () => {
const value = 'greetings';
const value2 = 'farewell';
const pair = 'salutations';
const inst = renderer.create(
<Provider value={value}>
<Testable pair={pair} />
</Provider>
);
const textInst = inst.root.findByType(Text);
expect(
textInst.props.children.join()
).toBe(`${value} & ${pair}`);
inst.update(
<Provider value={value2}>
<Testable pair={pair} />
</Provider>
);
expect(
textInst.props.children.join()
).toBe(`${value2} & ${pair}`);
});
});

Events

If you want to test any event props of a component, you will have to manually call them. You can do this by accessing the props from the element instance’s props object.

// components/Clickable.js
import React from 'react';
import { View, Text, TouchableHighlight } from 'react-native';
class Clickable extends React.Component { state = { number: 1 }; pressHandler = () => {
this.setState(prevState => (
{ number: prevState.number + 1 }
));
}
render() {
return (
<View>
<TouchableHighlight onPress={this.pressHandler}>
<Text>{this.state.number}</Text>
</TouchableHighlight>
</View>
);
}
}
export default Clickable;

The <Clickable> renders a <TouchableHighlight>, which has an onPress prop. Once we render the <Clickable>, we can use findByType to find the TouchableHighlight and call its onPress function.

We can either check that the state was updated by accessing it directly through the root instance’s instance.state or by checking the rendered text value.

describe('<Clickable>', () => {
it('updates number when clicked', () => {
const inst = renderer.create(<Clickable />);
const button = inst.root.findByType(TouchableHighlight);
const text = inst.root.findByType(Text);
// default state
expect(text.props.children).toBe(1);
// or
expect(inst.root.instance.state.number).toBe(1);
button.props.onPress(); // state was updated by the button press
expect(text.props.children).toBe(2);
});
});

--

--