Jigish Chawda
5 min readMay 11, 2017

This article assumes you have written react-native code and are exploring ways to write unit tests for the code you have already written. TDD may seem like a daunting task at this point in time. The author struggles with test driven development for react-native code.

We will be writing unit tests for the above-mentioned Login component.

For unit testing a component, we need to make sure the elements (child components) have testIDs. This is useful for finding the element from the react-component tree.

We use ‘react-test-renderer’ (node package) in order to render the component in a tree like structure.

From react-test-renderer

“This package provides an experimental React renderer that can be used to render React components to pure JavaScript objects, without depending on the DOM or a native mobile environment.”

An example of react-test-renderer.

Once we have the tree structure with us, we can find the required elements using testID. One can write utility functions to find elements by testID or by text based on our requirements.

Note: The contributors of the ‘react-test-renderer’ are working on selector APIs.

Let’s describe it…

Asserting the view element’s presence (or absence)

A specification for asserting on the presence of an element would look like:

A specification for asserting on the absence of an element would look like:

Asserting on the state

The state of the component changes on an event in the component or child component. The events could be component lifecycle methods or onPress events or any custom events. These events have callback handlers bound to them. In our login component: handleUsernameChange(), handlePasswordChange(), handleLoginPress() are the callback handlers bound to different events.

handlePasswordChange(text) {
this.setState({password: text})
}

In this callback, state variable password is modified. In order to make a call to this callback handler, we need the instance of the login component, so that we can explicitly call the handler and assert on the state change.

react-test-renderer provides a way to get the instance of the component.

let componentInstance = renderer.create(<Login />).getInstance()

To test the handlePasswordChange(), a specification would look like

Effects of props change

The change in props values has a certain effect on the child components. In our login component, if there is any API response error, the errorMessage is propagated from its parent component. To simulate this scenario, we create a stub parent component and propagate the errorMessage thus asserting on the effect.

Up till this point, we have not discussed the test runner which will run all our tests. While there are a couple of frameworks out there, we are going to stick with Jest[1].

// package.json
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native"
}

Jest provides certain capabilities which will help us test our components. For example, assertions used in the above examples so far comes from the jest. For more info on the Jest features and capabilities, refer Jest-API docs[2].

Understanding mocks

One of test cases for our Login component is to assert that the handleLogin prop was called on Login button press. However, we do not care about what the handleLogin function does. Hence, we can mock the function and assert on the method being called.

If one needs to assert on the parameters of the mock function then use mockFunction.mock.calls[3].

There are instances when a component is used within the component under test. If we need to bypass/override the behaviour of that component, we can use jest.mock(moduleName, factory, options)[4] to achieve it. This function can mock any module used in your codebase. The mock instance can then be ‘require’d within the specification. Refer to the example here[4].

- Testing event handlers like onPress

There will often be times when you would want to test the event handlers and their behaviour.

<TouchableHighlight
testID={'loginButton'}
onPress={this.handleLoginPress}>
<Text>
Login
</Text>
</TouchableHighlight>

At the first, it looks straight forward to test onPress handler. However, TouchableHighlight (or for that matter any TouchableXXX component) do not have onPress props in the tree. The reason for this is explained here well.

The Touchable components are finally rendered as View components and hence does not display all the props of the Touchable component. As the link suggests, the solution is to mock Touchable components so that the tree starts showing all the component’s props.

jest.mock('TouchableHighlight', () => {
const mockComponent = require('jest-react-native');
return mockComponent('TouchableHighlight');
});

As the onPress props start showing up in the tree, using appropriate testID, find the element and call the onPress props to test the behaviour.

But what about Actions and Reducers?

In the react world, using redux has become a norm. Hence, comes Actions and Reducers.

Actions are functions triggered due to events in components.

For example, on a press of login button, the parent component will dispatch an Action. This action could be to make a network call.

Reducers are functions which modify the application state and thereby trigger a change in the components.

For example, on network call failure for login request we will update the app state with errorMessage. This change in the value of errorMessage will trigger an update in the Login component to render the errorMessage.

I am assuming that the reader understands what dispatch() means in redux terminology.

Let’s look at Actions, first

handleConnectivityChange: function (isConnected) {
return { type: Constants.ACTIONS.HANDLE_CONNECTIVITY_CHANGE, isConnected: isConnected }
}

To test this piece of code, a specification could look like

Actions could be asynchronous, where the result is dispatched at some later time. Let’s take an example of login network call.

The doLogin call will dispatch actions asynchronously based on the result of the network call. To test the login success and failure scenarios, the specifications would look like

One can write a similar specification for login success scenario.

One thing to note in the specification is: ApiMock module. One of the ways to mock a module is to have an alternative implementation of a module under __mocks__ directory. The __mocks__ directory will be the sibling of the module file.

This way when you require the module once it is mocked in the spec file, it will return the mock implementation of the module.

In the spec file, we need to mock the Api module at the top level. And then in the spec, ‘require’ the same module.

jest.mock('../api/Api')it('should ...', () => {
let ApiMock = require('../api/Api') // returns the mock implementation of the module
})

It’s time to look at Reducers

A LoginReducer could look like

A simple specification would be

Anything else…

Tests for the javascript classes and objects should be written in a conventional way and wherever required mocks could be used.

References:

[1]Testing React Native Apps · Jest

[2]Jest — API docs

[3]Mock Functions · Jest

[4]The Jest Object · Jest — jest.mock

Note: This article is written based on the personal experience working on a react-native project. This could be improved with more project exposure and your valuable suggestions.