Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
Testing custom reusable components in React Native

Testing custom reusable components in React Native

Apr 11, 2021

React Native has emerged as a popular choice in the cross-platform mobile application space, as it saves between 40% and 90% of time spent developing apps on different platforms. As your application grows and the codebase expands, small errors and edge cases you don’t expect can cascade into larger failures. Bugs lead to bad user experiences and, ultimately, business losses. One way to prevent fragile programming is to test your code before releasing it into the wild.

In this article, we will cover how to test custom reusable components in React Native. Let’s get started!

Written by Sneh Pandya

Set up React Native application

Let’s get started by setting up our project. To create a new application, run the commands as follows:

npm install -g expo-cli // To add Expo CLI on your machine

npx react-native init ReactNativeComponentTesting // To set up a new React Native application

cd ReactNativeComponentTesting

Next, we will install testing dependencies, such as jest and react-test-renderer.

Let’s install the dependencies by running the command below in the terminal:

npm install react-test-renderer // React Test Renderer

npm install --save-dev jest     // Jest

npm install --save-dev @testing-library/react-native @testing-library/jest-native   // React Native Testing library

Create a reusable component

Let’s write a reusable React Native Button and test it. Suppose the reusable Button component contains the specifications below:

  • Fixed height and width with density-independent pixels

  • Primary buttons should have a blue background and white text

  • Secondary buttons should have a red background and white text

We also need to add property controls to this button for greater usability, flexibility and unplanned changes. The newly added properties would be:

  • ID: unique identifier in the app to connect business logic

  • onPress: a function will be triggered on button press

Let’s have a look at how the component will be built:

import react from "react";
import { TouchableOpacity, Text } from "react-native";

export default function Button(props) {
 
  // property declaration
  const { title, onPress, primary, secondary, height, width } = props;

  return (
    <TouchableOpacity onPress={onPress}>
      <Text>{title}</Text>
    </TouchableOpacity>
  );
}

Now, with the default function in place, let’s add styling and different variants.

import React from 'react';
import {TouchableOpacity, Text} from 'react-native';

export default (Button = props => {
  
  // added property declarations
  const { title, onPress, secondary, large, height, width, baseColor, textColor } = props;

  if (!title) return new Error('No title added!');

  // added conditions
  const HEIGHT = large ? 60 : height ? height : 40;
  const WIDTH = width ? width : 200;
  const BACKGROUND_COLOR = secondary ? 'red' : baseColor ? baseColor : 'blue';
  const TEXT_COLOR = textColor ? textColor : 'white';

  return (
    <TouchableOpacity
      // added styling
      style={{
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: BACKGROUND_COLOR,
        height: HEIGHT,
        width: WIDTH,
      }}
      onPress={onPress}>
      <Text style={{color: TEXT_COLOR}}>{title}</Text>
    </TouchableOpacity>
  );
});

The above code snippet has added property declarations and conditions. Let’s look at them in detail below:

  • For height: If large is true, then set HEIGHT to 60. If height is true, then set HEIGHT as height, or else set height to 40.

  • For width: If width is true, then set width as width, or else set width to 200.

  • For BACKGROUND_COLOR: If secondary is true, then set BACKGROUND_COLOR to red. If baseColor is true, then set BACKGROUND_COLOR as baseColor, or else set BACKGROUND_COLOR to blue.

  • For TEXT_COLOR: If TEXT_COLOR is true, then set TEXT_COLOR as textColor, or else set TEXT_COLOR to white.

Set up reusable component in React Native

With the growing number of properties and features in the application, even simple reusable components like Button can become complicated as a result of mutations and possible combinations. Let’s have a look at how we would use this component in our app:

import React from 'react';
import {View, Text, Dimensions, Alert} from 'react-native';

import Button from './src/Button';

const {height, width} = Dimensions.get('screen');

const App = () => {
  return (
    <View
      style={{height, width, alignItems: 'center', justifyContent: 'center'}}>
      {/* primary button */}
      <Text>Primary</Text>
      <Button title="Test Button" />

      {/* large primary button */}
      <Text>Primary Large</Text>
      <Button title="Test Button" large />

      {/* secondary button */}
      <Text>Secondary</Text>
      <Button title="Test Button" secondary />

      {/* secondary button */}
      <Text>Secondary Large</Text>
      <Button title="Test Button" secondary large />

      {/* button with custom width and height */}
      <Text>custom width and height</Text>
      <Button title="Test Button" height={100} width={300} />

      {/* button with custom baseColor and custom textColor */}
      <Text>Custom colors</Text>
      <Button title="Test Button" baseColor="lightpink" textColor="purple" />

      {/* button with alert callback function */}
      <Text>with onPress callback</Text>
      <Button
        title="Test Button"
        onPress={() => Alert.alert('Button pressed')}
      />
    </View>
  );
};

export default App;

Execute test with Jest

It’s time to set up the testing configuration for our code. We have already installed all the required dependencies while setting up the application. Create a new file called Button.test.js, and add the test case as shown below:

import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';

import Button from './Button';

describe('Testing custom button', () => {
  const wrapper = renderer.create(<Button title="Test Button" />);

  it('Should render', () => {
    expect(wrapper.toJSON()).toBeTruthy();
  });
});

At the top of the file, renderer is imported from react-test-renderer, which provides a container (or wrapper) for our custom component.

describe marks the start of a new test suite in Jest. The first argument is a String containing a description of what the encompassing test suite is testing. The second argument is a callback function where the test execution steps are written.

it defines the start of a new test case in Jest. Tests should be as small and concise as they can be, and they should only test one thing. The arguments are the same as those of describe – a String defining the test scenario and a callback function.

The .toJSON() and the .toBeTruthy() assertion function provided by Jest are written inside of the it block. This will check if the value is not null or undefined.

To test the custom reusable Button, the different use cases can be:

  • Primary button: height 40, width 200, baseColor blue, textColor white

  • Secondary button: height 40, width 200, baseColor red, textColor white

  • Size: button can be large or custom width

  • Custom baseColor: can be applied to both the primary and secondary button

  • Custom textColor: can be applied to both the primary and secondary button

Below is the test suite for the primary button. For brevity, the test suite of the secondary button is skipped because the methods for the secondary button are the same.

import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';

import Button from './Button';

describe('Testing primary button', () => {
  const wrapper = renderer.create(<Button title="Test Button" />);

  console.log(wrapper.toJSON())

  const styles = wrapper.toJSON().props.style;

  const { height, width, backgroundColor } = styles;

  const childStyles = wrapper.toJSON().children[0].props.style;

  const {color: buttonTextColor} = childStyles;

  it('Should render', () => {
    expect(wrapper.toJSON()).toBeTruthy();
  });

  it('Should have height of 40', () => {
    expect(height).toBe(40);
  });

  it('Should have width of 200', () => {
    expect(width).toBe(200);
  });

  it('Should have blue background', () => {
    expect(backgroundColor).toBe('blue');
  });

  it('Should have white text', () => {
    expect(buttonTextColor).toBe('white');
  });
});

Let’s say we want to test that our textColor is what we expected. The line console.log(wrapper.toJSON()) gives the output shown below. This is useful for testing our style and other elements, like children.

{
    type: 'View',
    props: {
        accessible: true,
        style: {
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'blue',
        height: 40,
        width: 200,
        opacity: 1
        }
         ...
        children: [ { type: 'Text', props: [Object], children: [Array] } ]
    }
}

Let’s look at the next example of testing that the textColor is specified as we expected. From the log above, children is an array of all the children seen from the root node in the render tree. Moreover, there is only one child in this case, and the styles can be fetched from children with the line of code below:

console.log('Children styles:=>', wrapper.toJSON().children[0].props.style);

children[0] returns the first object in the array, and props.style returns the style object. The output would look like this:

Children styles:=> { color: 'white' }

Since the structure becomes clearer with the above output, we can write the declaration and the same type of test case as shown above:

  // declaration
  const { color: buttonTextColor } = childStyles;

  // test case
  it('Should have white text', () => {
    expect(buttonTextColor).toBe('white');
  });

After understanding these techniques, writing tests using Jest and React Native becomes easier for all the other combinations of custom reusable Buttons. Run the test using the command below:

yarn run test

Voila! The tests are written and successfully tested, with all tests passing!

Conclusion

We have covered the basics of custom reusable component (Button) testing for React Native with various approaches. This article should be helpful for writing test cases and test suites for any similar components with multiple properties and combinations. With Jest, testing React Native applications has become easier and more rapid and consistent regardless of the styles declared.


Sneh is a Senior Product Manager based in Baroda. He is a community organizer at Google Developers Group and co-host of NinjaTalks podcast. His passion for building meaningful products inspires him to write blogs, speak at conferences and mentor different talents. You can reach out to him over Twitter (@SnehPandya18) or via email (sneh.pandya1@gmail.com).

Latest articles

Show more posts