Streamline Your Testing Process: A Step-by-Step Guide to Unit Testing and Snapshot Testing For React Native App with Jest

Streamline Your Testing Process: A Step-by-Step Guide to Unit Testing and Snapshot Testing For React Native App with Jest

An Overview of Unit Testing and Snapshot Testing with Jest

React Native provides developers with a flexible platform for building mobile apps that can run on both iOS and Android. When it comes to testing these apps, Jest has become a go-to choice for many developers. Jest is a testing system that is pre-installed with React Native and is quick, simple to use, and dependable. We'll look at using Jest to evaluate React Native applications in this blog post.

Setting Up Jest for React Native Testing

To get started you must install Jest in your project before using it to test React Native. Run the following command in your project directory to accomplish this:

npm install --save-dev @testing-library/jest-native

Once you have installed Jest, open your package.json folder to update Jest configuration:

// package.json
{
  "preset": "react-native",
  "setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"]
}

Setting up for Expo

npx expo install jest-expo jest

Then update your package.json

"scripts": {
  ...
  "test": "jest"
},
"jest": {
  "preset": "jest-expo"
}

Writing Snapshot Tests

Snapshot testing is a technique for testing user interfaces that involves comparing a rendered component's "snapshot" to a previously saved snapshot. If the snapshots match, the test passes; if they don't match, the test fails. Snapshot testing is an effective way to check for unintentional UI changes and regressions.

To get started, let's create our components. To test our component, we will create two separate components - one that represents the component we are testing, and another that serves as the test case.

Now let's create a file and a test file. The test file and our file should be named in the format FileName.test.js and FileName.js, respectively. Our test file must be placed in a folder named __tests__.

Let's test for a Goal Creating App. Below is our folder structure

__tests__/Layout.test.js
components/Layout.js

Layout.js

import { useState } from "react";
import { Button, StyleSheet, Text, TextInput, View } from "react-native";

export default function Layout() {
  const [enteredGoalText, setEnteredGoalText] = useState("");
  const [courseGoal, setCourseGoal] = useState([]);
  function goalInputHandler(enteredText) {
    setEnteredGoalText(enteredText);
  }
  function addGoalHandler() {
    setCourseGoal((currentGoal) => [...currentGoal, enteredGoalText]);
  }

  return (
    <View style={styles.appContainer}>
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.textInput}
          placeholder="Your Goals Today"
          onChangeText={goalInputHandler}
        />
        <Button title="Add Goal" onPress={addGoalHandler} />
      </View>
      <View style={styles.textContainer}>
        {courseGoal.map((goal) => (
          <Text key={goal.id}>{goal}</Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1,
    paddingTop: 50,
    paddingHorizontal: 16,
  },
  inputContainer: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: 24,
    borderBottomWidth: 1,
    borderBottomColor: "#cccccc",
    flex: 1,
  },
  textInput: {
    borderWidth: 1,
    width: "70%",
    borderColor: "#cccccc",
    marginRight: 8,
    padding: 10,
  },
  textContainer: {
    flex: 5,
  },
});

Let's create our test case. In the test file (Layout.test.js), we are going to import our Layout.js component for testing. For testing purposes, we need to use React's test renderer and Jest's snapshot (toMatchSnapshot()) function. These two functions will work together to capture a snapshot of our rendered component and compare it to a previously saved snapshot."

Below is our test file

Layout.test.js

import React from "react";
import { render, screen, fireEvent } from '@testing-library/react-native';
import Layout from "../components/Layout";

describe("Layout", () => {
  test("renders correctly", () => {
    const { toJSON } = render(<Layout />);
    expect(toJSON()).toMatchSnapshot();
  });
});

To test we will Run npm run test command, and if everything goes well, you should see a snapshot created and one test passed.

Writing Unit Tests

Software development uses unit tests, a sort of automated testing that enables you to test specific sections or functions of your code separately. Unit testing is an essential part of any software development process, and React Native is no exception.

By testing individual units or functions of your code in isolation, you can catch bugs and errors early in the development process, leading to more reliable and efficient code.

Unit Test for input field and button

Let's write unit tests for our previously created component, Layout.js. We want to ensure that the component contains an input field and a button. We can achieve this by using the getByTestId function to retrieve the elements with the testID attributes of "input" and "button". Additionally, we will create a function called toBeDefined to check if the elements are defined.

To use the testID attribute in our Layout.js component, we need to update it with appropriate testID names

Layout.js

import { useState } from "react";
import { Button, StyleSheet, Text, TextInput, View } from "react-native";

export default function Layout() {
  const [enteredGoalText, setEnteredGoalText] = useState("");
  const [courseGoal, setCourseGoal] = useState([]);
  function goalInputHandler(enteredText) {
    setEnteredGoalText(enteredText);
  }
  function addGoalHandler() {
    setCourseGoal((currentGoal) => [...currentGoal, enteredGoalText]);
  }

  return (
    <View style={styles.appContainer}>
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.textInput}
          placeholder="Your Goals Today"
          onChangeText={goalInputHandler}
          testID="input"
        />
        <Button testID="button" title="Add Goal" onPress={addGoalHandler} />
      </View>
      <View style={styles.textContainer}>
        {courseGoal.map((goal) => (
          <Text  key={goal.id}>{goal}</Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1,
    paddingTop: 50,
    paddingHorizontal: 16,
  },
  inputContainer: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: 24,
    borderBottomWidth: 1,
    borderBottomColor: "#cccccc",
    flex: 1,
  },
  textInput: {
    borderWidth: 1,
    width: "70%",
    borderColor: "#cccccc",
    marginRight: 8,
    padding: 10,
  },
  textContainer: {
    flex: 5,
  },
});

Update theLayout.test.js component

Layout.test.js

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

import Layout from "../components/Layout";

describe("Layout", () => {
  test("renders correctly", () => {
    const { toJSON } = render(<Layout />);
    expect(toJSON()).toMatchSnapshot();

  });
  test('renders input and button', () => {
    const { getByTestId } = render(<Layout />);
    const input = getByTestId('input');
    const button = getByTestId('button');

    expect(input).toBeDefined();
    expect(button).toBeDefined();
  });
});

To test we will Run npm run test command, and if everything goes well, you should see an output indicating two tests passed.

Test to Check Newly Added Goal

We are going to be creating another test to check when a user input a goal we are going to be using courseGoal ID to access goal added. This is accomplished by changing the input field's text and simulating a button press using the fireEvent method. This test checks to see if, when the button is clicked, the component adds a new goal to the list.

Let's update our Layout.js view with a TestID courseGoal in other to have access to the input text

Layout.js

import { useState } from "react";
import { Button, StyleSheet, Text, TextInput, View } from "react-native";

export default function Layout() {
  const [enteredGoalText, setEnteredGoalText] = useState("");
  const [courseGoal, setCourseGoal] = useState([]);
  function goalInputHandler(enteredText) {
    setEnteredGoalText(enteredText);
  }
  function addGoalHandler() {
    setCourseGoal((currentGoal) => [...currentGoal, enteredGoalText]);
  }

  return (
    <View style={styles.appContainer}>
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.textInput}
          placeholder="Your Goals Today"
          onChangeText={goalInputHandler}
          testID="input"
        />
        <Button testID="button" title="Add Goal" onPress={addGoalHandler} />
      </View>
      <View testID="courseGoal" style={styles.textContainer}>
        {courseGoal.map((goal) => (
          <Text  key={goal.id}>{goal}</Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  appContainer: {
    flex: 1,
    paddingTop: 50,
    paddingHorizontal: 16,
  },
  inputContainer: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: 24,
    borderBottomWidth: 1,
    borderBottomColor: "#cccccc",
    flex: 1,
  },
  textInput: {
    borderWidth: 1,
    width: "70%",
    borderColor: "#cccccc",
    marginRight: 8,
    padding: 10,
  },
  textContainer: {
    flex: 5,
  },
});

Let's update out Layout.test.js with the function to test for the entered goals. we will use a fireEvent.changeText function to simulate the user typing and fireEvent.press function to simulate the user clicking the "Add Goal" button. Finally, it will use the expect function to check that the courseGoal element has the text content "Learn Jest"

Layout.test.js

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

import Layout from "../components/Layout";

describe("Layout", () => {
  test("renders correctly", () => {
    const { toJSON } = render(<Layout />);
    expect(toJSON()).toMatchSnapshot();

  });
  test('renders input and button', () => {
    const { getByTestId } = render(<Layout />);
    const input = getByTestId('input');
    const button = getByTestId('button');

    expect(input).toBeDefined();
    expect(button).toBeDefined();
  });
  test('adds goal to list when button is clicked', () => {
    const { getByTestId } = render(<Layout />);
    const input = getByTestId('input');
    const button = getByTestId('button');
    const courseGoal = getByTestId('courseGoal');

    fireEvent.changeText(input, 'Learn Jest');
    fireEvent.press(button);
    expect(courseGoal).toHaveTextContent('Learn Jest');

  });
});

Finally let's run npm run test command, and if everything goes well, you should see an output indicating three tests passed.

Conclusion

In conclusion, Jest is a powerful and widely used testing framework that developers can use to test React Native applications. Jest is simple to use, quick, and dependable, and it is pre-installed with React Native, making it a popular choice for testing React Native apps. In this blog post, we explored how to set up Jest for React Native testing and how to use snapshot testing and unit testing to test our components. Snapshot testing is an effective way to check for unintended UI changes and regressions, while unit testing helps catch bugs and errors early in the development process. By using Jest to test your React Native apps, you can ensure that your code is reliable, efficient, and bug-free.