Before you go, check out these stories!

0
Hackernoon logoBeginners Guide to Get Started with Unit Testing in React Native by@josepholabisi

Beginners Guide to Get Started with Unit Testing in React Native

Author profile picture

@josepholabisiJoseph Olabisi

Software developer, driven by innovation

Automated tests give you confidence in the piece of code you write. Sometimes we write code that works locally then push to production. However, changes made breaks entirely different parts of the project. 
Someone else on your team might have written these parts of the project, but your changes caused conflicts. Testing prevents this.

There are different types of tests: unit tests, integration tests, functional tests, end-to-end tests, and many more.

What is unit testing?

Using a car as an analogy, we all know that a car comprises of different components (Unless you live in a tree 🤪). These components comprise wheels, an engine, headlights, and taillights, etc.

Unit tests basically ensure each individual component performs as expected. Looking at our car analogy, to ensure our wheel is perfectly round we rotate it by exerting external force, same way we write unit tests to ensure individual components of our source code (software) function as they ought to.

In this article, we will be looking at the basic principles of unit tests with a simple react-native Button component.

Let’s go ahead to create a Button component.

import React from 'react';
import {Pressable, Text, View, StyleSheet} from 'react-native';

const Button = ({onPress, title, isLoading, transparent}) => {
  return (
    <Pressable
      onPress={() => {
        if (!isLoading) {
          return onPress();
        }
      }}>
      <View style={transparent ? styles.transparentBtn : styles.btn}>
        <Text style={transparent ? styles.transparentTitle : styles.title}>
          {isLoading ? 'Loading...' : title}
        </Text>
      </View>
    </Pressable>
  );
};

const styles = StyleSheet.create({
  btn: {
    width: '100%',
    height: 50,
    backgroundColor: '#74B3CE',
    alignItems: 'center',
    justifyContent: 'center',
    marginVertical: 10,
    borderRadius: 4,
  },
  transparentBtn: {
    width: '100%',
    height: 50,
    backgroundColor: 'transparent',
    alignItems: 'center',
    justifyContent: 'center',
    marginVertical: 10,
  },
  title: {
    color: 'white',
    fontSize: 16,
  },
  transparentTitle: {
    color: '#74B3CE',
  },
});

export default Button;

Testing

We test react-native using several tools. The first tool we will look at is Jest.

Jest — Javascript testing framework.

There are different testing frameworks, depending on the programming language tested. ‘Jest’ is one of the most preferred testing frameworks for React and React-native applications. There are other Javascript testing frameworks; Mocha, Jasmine, Karma, Puppeteer.

React Native ships with Jest; we can skip its installation.

‘Jest’ discovers test files within our project via their file names, which can be at any depth in our project. There are three naming styles by which ‘Jest’ picks up our test files.

Any file with a .test.js suffix or a .spec.js suffix — This is suitable for finding associated test files for a component. When you have App.js the test file is side-by-side as App.test.js or App.spec.jsAny files witing a test folder — This is preferrable for components that require multiple test files and large projects.

Enzyme — Enzyme helps test the output (rendering), interactivity, and manipulation of our React and React native components by simulation, as compiling and running our react-native application for testing would be a slow process. Enzyme’s syntax makes our lives easier while testing components 😁. An alternative to Enzyme is ‘react-test-renderer’, but we will make use of Enzyme as it contains more functionalities.

To setup enzyme first run:

npm install jest-environment-enzyme jest-enzyme enzyme-adapter-react-16 — save-dev

npm install — save-dev react-dom enzyme

Edit package.json — Add the following config to “jest” object

"jest": {
        "setupFilesAfterEnv"["jest-enzyme"],
        "testEnvironment": "enzyme",
        "testEnvironmentOptions": {
          "enzymeAdapter": "react16"
        }  
    }

So lets get to it!

Testing our Button component

import React from 'react';

import Button from '../index';
import {View, Text} from 'react-native';
import {shallow} from 'enzyme';
import toJson from 'enzyme-to-json';

describe('Button', () => {
  it('should render without issues', () => {
    const component = shallow(<Button />);

    expect(component.length).toBe(1);
    expect(toJson(component)).toMatchSnapshot();
  });

  //...

describeitexpect- Are global variables in ‘Jest’; as a result, we don’t have to require or import anything to use them.

describe(name, cb)- This method creates a block or scope where we write several related tests. 'describe' takes in a name (should be descriptive) and then a callback where we write our actual tests

it(name, cb, timeout)- ‘it’ is an alias of test which has three parameters — a descriptive name of your choice, a callback function containing our test assertions, and an optional timeout that specifies how long to wait before the test aborts. The default timeout is 5 seconds.

expect(value) - Expect is used when we want to test a value, we mostly use 'expect' alongside other functions called "matchers", to view all matchers you can check the link Expect · Jest

Testing “Button”, we call the describe method passing in a description and a callback indicating a shared relationship of every test assertions in the describe block. Then our first test assertion — it(‘should render without issues’, ….) makes sure the Button renders.

Using shallow from Enzyme, we run the Button component in isolation without rendering its children (In this instance, Button has no child component), shallow calls the constructor of the component and the component’s render method.

The number of components rendered (component.length) is expected to equal one using expect and the toBe() matcher. toBe() compares values or object instances, making it more suitable to the === operator.

Snapshots —We use snapshots to make sure we don’t cause unexpected changes to UI components. When we make deliberate changes to components, the snapshots test fails; as a result, we update our previous snapshots using npm test — -u

We use the .toMatchSnapshot() to compare previous and current snapshots. When there isn’t an old snapshot, ‘Jest’ creates a new one.

To compare snapshots we use the toJson(component) method; it returns a compatible format of our component for ‘Jest’ snapshots.

The next thing we want to test is the onPress event prop, but before doing, that let’s look at Mock functions in Jest.

Mock functions — In Jest, mock functions allow us to test links between code. Jest mock functions let us erase the actual implementation, more like a placeholder. For example, testing a component that makes actual API calls, our test will be slow; hence we mock the API endpoint.

There are two ways to mock functions in ‘Jest’:

Creating mock functions in test scripts.Writing manual mocks to override dependencies.

For now, we will mock up a simple function below.

We track of mock functions calls and return values; using the .mock property. This is done using the .calls.return.instances properties.

//...

  it('should call onPress event', () => {
    const result = 'I was Pressed';
    const mockFn = jest.fn(() => result);
    const component = shallow(<Button onPress={mockFn} />);

    expect(mockFn).not.toHaveBeenCalled();

    component.props().onPress();

    expect(mockFn.mock.calls.length).toBe(1);
    expect(component.props().onPress()).toBe(result);
  });

  //...

We create a mock function which returns ‘I was Pressed’, then render our component using shallow(). Afterwards simulate a press by calling .props().onPress(). The method .props returns an object containing all props of a component. Considering the normal behavior of a button, pressing the Button component only once should call our mock function — which returns ‘I was Pressed’. To test for this, expect() is called twiceEnsuring mockFn is called once, and its return value is correct.

The next thing we want to make sure of is onPress not calling mockFnwhen isLoading is true.

//...

  it('should not call onPress event when loading is true', () => {
    const result = 'I was pressed';
    const mockFn = jest.fn(() => result);

    const component = shallow(<Button isLoading={true} onPress={mockFn} />);

    component.props().onPress();
    expect(mockFn.mock.calls.length).toBe(0);
  });

//...

When isLoading prop is true, we simulate a press on Button, which shouldn’t trigger a call to mockFn. Therefore the number of calls to mockFn remains 0.

Next, we test if Button renders the correct title.

//...

  it('button should render title passed to it', () => {
    const title = 'Primary Button';
    const component = shallow(<Button title={title} />);

    expect(component.find(Text).first().props().children).toBe(title);
  });

//...

We use the .find method to obtain the Text component in Button, the child of Text should be the same as the title prop.

Next, we test if the title of Button renders ‘Loading…’ when isLoading=== true

//...

it('should be Loading... text when isLoading === true', () => {
    const component = shallow(<Button isLoading={false} />);
    component.setProps({isLoading: true});
    component.update();
    expect(component.find(Text).first().props().children).toBe('Loading...');
  });

//...

We use setProps to update the props passed to Button and force a re-render, then we check if the title rendered is “Loading…”

Next, we test if the styles of Button change when we pass the transparentprop to Button.

//...

  it('should have transparent styles when transparent is true', () => {
    const transparentContainer = {
      width: '100%',
      height: 50,
      backgroundColor: 'transparent',
      alignItems: 'center',
      justifyContent: 'center',
      marginVertical: 10,
    };

    const transparentTitle = {
      color: '#74B3CE',
    };

    const component = shallow(<Button transparent />);
    expect(component.children(View).props('style').style).toMatchObject(
      transparentContainer,
    );

    expect(
      component.children(View).children(Text).props('style').style,
    ).toMatchObject(transparentTitle);
  });
});

We use the .toMatchObject to check if our style objects are correct.

Finally, we test by running npm run test

Our test cases pass, yeee! 😝

Conclusion

Testing is like our best friend, saving us from a lot of troubles. Testing our code prevents us from pushing breaking changes to production, forces us to write quality code, serves as a form of documentation, find bugs early, and a lot of other advantages.

Resources

For further readings and API references, check out the links below:

Jest · 🃏 Delightful JavaScript Testing

Testing · React Native

Testing React Native Apps · Jest

Also published at: https://medium.com/react-native-nigeria/getting-started-with-unit-testing-in-react-native-1fb2d0add259

Lead Photo by Alvaro Reyes on Unsplash

Tags

Become a Hackolyte

Level up your reading game by joining Hacker Noon now!