Hachther Blog > Développement Web  > Getting started unit testing in React native applications with Jest & Testing Library

Getting started unit testing in React native applications with Jest & Testing Library

What is the React Native Testing Library ?

The React Native Testing Library (RNTL) is a lightweight solution for testing React Native components. It provides light utility functions on top of react-test-renderer, in a way that encourages better testing practices.

Installation:

Open a Terminal in your project’s folder and run:

npm install --save-dev @testing-library/react-native
or
yarn add --dev @testing-library/react-native

This library has a peerDependencies listing for react-test-renderer and, of course, react. Make sure to install them too!

To using Jest:

Add @testing-library/jest-native package to your project, in order to use addtional React Native-specific jest matchers :

npm install --save-dev @testing-library/jest-native
or
yarn add --dev @testing-library/jest-native

Then automatically add it to your jest tests by using setupFilesAfterEnv option in your Jest configuration (it’s usually located either in package.json under "jest" key or in a jest.config.js file):

{
  "preset": "react-native",
  "collectCoverage": true,
  "coveragePathIgnorePatterns": ["/node_modules/", "/jest"],
  "setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"],
  "transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|@?react-native)"]
}
  • collectCoverage : indicates whether the coverage information should be collected while executing the test. Because this retrofits all executed files with coverage collection statements, it may significantly slow down your tests. For example :
  • coveragePathIgnorePatterns : An array of regexp pattern strings that are matched against all file paths before executing the test. If the file path matches any of the patterns, coverage information will be skipped.
  • setupFilesAfterEnv : A list of paths to modules that run some code to configure or set up the testing framework before each test file in the suite is executed.
  • transformIgnorePatterns : An array of regexp pattern strings that are matched against all source file paths before transformation. If the file path matches any of the patterns, it will not be transformed.

Create component:

Let’s create a QuestionsBoard component to illustrate and simulate a unit test :

//QuestionsBoard.js

import * as React from 'react';
import {
    View,
    TouchableOpacity,
    Text,
    ScrollView,
    TextInput,
} from 'react-native';

function QuestionsBoard({ questions, onSubmit }) {
    const [data, setData] = React.useState({});

    return (
        <ScrollView>
            {questions.map((q, index) => {
                return (
                    <View key={q}>
                        <Text>{q}</Text>
                        <TextInput
                            accessibilityLabel="answer input"
                            accessibilityHint="input"
                            onChangeText={(text) => {
                                setData((state) => ({
                                    ...state,
                                    [index + 1]: { q, a: text },
                                }));
                            }}
                        />
                    </View>
                );
            })}
            <TouchableOpacity onPress={() => onSubmit(data)}>
                <Text>Submit</Text>
            </TouchableOpacity>
        </ScrollView>
    );
}

export default QuestionsBoard;

Test the functioning of the components:

In a QuestionsBoard-test.js file, Let’s test components to see what unit testing is about..

//QuestionsBoard-test.js

import 'react-native';
import React from 'react';
import App from '../App';
import { render, fireEvent } from '@testing-library/react-native';

describe('QuestionsBoard', function () {
    it('should display snapshoot', function () {
        const allQuestions = ['q1', 'q2'];

        const { toJSON } = render(
            <App questions={allQuestions} />
        );
        expect(toJSON()).toMatchSnapshot();
    });
});

This test is the snapshot test that captures the rendering of the component in a file. When you run yarn test or npm run test, this will produce an output file like this:

// QuestionsBoard-test.js.snap

exports[`QuestionsBoard should display snapshoot 1`] = `
<RCTScrollView>
  <View>
    <View>
      <Text>
        q1
      </Text>
      <TextInput
        accessibilityHint="input"
        accessibilityLabel="answer input"
        allowFontScaling={true}
        onChangeText={[Function]}
        rejectResponderTermination={true}
        underlineColorAndroid="transparent"
      />
    </View>
    <View>
      <Text>
        q2
      </Text>
      <TextInput
        accessibilityHint="input"
        accessibilityLabel="answer input"
        allowFontScaling={true}
        onChangeText={[Function]}
        rejectResponderTermination={true}
        underlineColorAndroid="transparent"
      />
    </View>
    <View
      accessible={true}
      focusable={true}
      onClick={[Function]}
      onResponderGrant={[Function]}
      onResponderMove={[Function]}
      onResponderRelease={[Function]}
      onResponderTerminate={[Function]}
      onResponderTerminationRequest={[Function]}
      onStartShouldSetResponder={[Function]}
      style={
        Object {
          "opacity": 1,
        }
      }
    >
      <Text>
        Submit
      </Text>
    </View>
  </View>
</RCTScrollView>
`;

These snapshot tests prevent regressions by comparing the actual characteristics of an application or component with the “correct” stored values for those characteristics. Snapshot tests are fundamentally different from unit and functional tests.

Our second test is the interaction test. We have in our QuestionsBoard.js file, a controlled text input (TextInput) that calls the prop onChangeText and a wrapper (TouchableOpacity) that calls the prop onSubmit. In order to test it, provide mock functions to these props:

//QuestionsBoard-test.js

import 'react-native';
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import QuestionsBoard from '../src/QuestionsBoard';

describe('QuestionsBoard',  () => {
    it('should display snapshoot', () => {
        const allQuestions = ['q1', 'q2'];
        const mockFn = jest.fn();

        const { toJSON } = render(
            <QuestionsBoard questions={allQuestions} onSubmit={mockFn} />
        );
        expect(toJSON()).toMatchSnapshot();
    });

    it('form submits two answers', () => {
        const allQuestions = ['q1', 'q2'];
        const mockFn = jest.fn();

        const { getAllByA11yLabel, getByText } = render(
            <QuestionsBoard questions={allQuestions} onSubmit={mockFn} />
        );

        const answerInputs = getAllByA11yLabel('answer input');

        fireEvent.changeText(answerInputs[0], 'a1');
        fireEvent.changeText(answerInputs[1], 'a2');
        fireEvent.press(getByText('Submit'));

        expect(mockFn).toBeCalledWith({
            '1': { q: 'q1', a: 'a1' },
            '2': { q: 'q2', a: 'a2' },
        });
    });
});

Let’s conclude with the following result, which shows us that the components and props implemented in our QuestionsBoard.js file have been 100% covered .

No Comments

Leave a reply