Skip to main content

Testing Ignite Apps

At Infinite Red, we want confidence that the code we are shipping isn't breaking the experience for our clients' users.

Our philosophy is roughly based on the following idea from Guillermo Rauch:

Write tests. Not too many. Mostly integration.

This isn't a hard and fast rule, but it does express our approach fairly well.

Maestro Testing

We provide an Ignite Cookbook recipe to help explain how to get started and run Maestro tests once you have ignited your app

Unit Testing

Unit tests cover the smallest parts of code, like individual functions or classes.

-- React Native docs, under CC By 4.0

Test Structure

In Ignite, we include unit tests for pure functions, such as models or utility functions.

Ignite uses Jest as our test runner. Jest tests are written using it or test statements, which take a describe of the test, and a function to execute the test code.

Then, inside the test function, we can make "assertions", or what we expect a value to be using the expect function. We pass the value as the first argument to the expect function, then we use one of the "matcher" methods on expect, such as .toBe to describe what the value should match.

The React Native doc provide the following example for a unit test:

it("given a date in the past, colorForDueDate() returns red", () => {
const input = colorForDueDate("2000-10-20")
expect(input).toBe("red")
})

Jest functions like it, test, expect, and more are loaded globally by the Jest test runner, so you don't need to import them.

Best Practices

When writing a test, do your best to make sure that your tests > include the following information:

  • Given - some precondition
  • When - some action executed by the function that you’re testing
  • Then - the expected outcome This is also known as AAA (Arrange, Act, Assert).

-- Structuring Tests in React Native Docs

You can read more about how to best practices for creating tests in the Structuring Tests section of the React Native docs.

Writing Tests

To write your own tests, create a .test.ts file within the app or test directory.

Then run yarn test to run all unit tests using Jest.

When writing tests, you can also run Jest in watch mode by running yarn test:watch. This will start a long running Jest process, that re-runs your tests on save in your editor. This is useful for iterating on values and getting quick feedback about whether your changes were successful or not.

When to write Unit Tests

The most important question to ask when writing tests is "what code should be unit tested?" Not every line of code will benefit from a unit test. Typically, you'll want a unit test when you have code that can be run without external dependencies (like an API) that has some non-trivial logic.

  • complicated regexes: for many developers, regex's only make sense as you are writing them. Testing with a series of valid and invalid inputs can help ensure that they work as intended for future developers.
  • nested if/else statements: if you rely heavily on a function with a lot of if/else statements, it can be helpful to have tests to make sure that you can visit each condition. Often times when we have more than a handful of conditionals, it can become impossible to visit all of them without realizing it.
  • validation functions: often times we may write functions like isJson() to validate that a value is a specific shape. If critical parts of our code rely on the correctness of this function, we want to test that code!

Mocking

However, often our code is not entirely written as pure functions. Most apps have side effects like making network requests, calling native modules, or reaching for global objects.

In integration tests, we can set up a suitable testing environment to handle these side effects.

But in unit tests, where we want to test an isolated piece of code, another option is to provide mocks for these external dependencies.

Jest provides a variety of mocking strategies for our code:

Mock Functions

You can create a mock callback function in Jest like so:

// take the input value, and add 42
const mockCallback = jest.fn((x) => 42 + x)

This callback can be called like a normal function

const added = [0, 1].map(mockCallback)

But it also has a variety of properties added to it, such as .mock, which you can assert against later in tests.

// The mock function is called twice, once for each item in the array
expect(mockCallback.mock.calls.length).toBe(2)

This particular example from the official Jest docs. You can read more about about what other properties are available on the .mock property as well at the Jest docs.

Mock Modules

Testing code that touches other libraries such as axios can be challenging, because we need to rely on the network for what gets returned from users.

import { View, Text } from "react-native"
import axios from "axios"

export const getUsers = () => axios.get("/users").then((res) => res.data)

One way to solve this, is by mocking the axios library to return a static list of users, so that we can reliably get the same information in our tests.

import axios from "axios"
import { getUsers } from "./users"

jest.mock("axios")

test("should fetch users", () => {
const users = [{ name: "Bob" }]
const res = { data: users }

axios.get.mockImplementation(() => Promise.resolve(res))

getUsers().then((data) => {
expect(data).toEqual(users)
})
})

This example is derived from the the Mocking Modules section of the Jest docs, where you can read about more sophisticated use cases.

React Native Modules

In addition to regular Javascript libraries, you can also mock out native modules in React Native. Using the following syntax

jest.mock('react-native-video', () => 'Video');

The first argument of jest.mock is the name of the module want to mock, but you can also pass a second argument to provide a function that returns the module.

In this example, this will return a default export.

This example is derived from Testing React Native section of the Jest docs, where you can read more.

Resources

Libraries

There are a variety of testing libraries available in React Native that you may find useful to add to your Ignite app

Relevant Reading