Testing the useState hook in React Native

Posted in react-native-testing-library, quality-assurance on July 2, 2023 by Hemanta Sapkota ‐ 5 min read

Testing the useState hook in React Native

As a React Native developer, you might need to test the useState hooks to ensure that the state updates correctly and that the component renders as expected based on the state.

Step 1: Write the component code in React Native

Here is an example code for the component:

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

function ExampleComponent() {
  const [isLoading, setIsLoading] = useState(true);
  const [hasError, setHasError] = useState(false);

  if (isLoading) {
    return (
      <View>
        <Text>Loading...</Text>
      </View>
    );
  }

  if (hasError) {
    return (
      <View>
        <Text>Error!</Text>
      </View>
    );
  }

  return (
    <View>
      <Text>Data Loaded!</Text>
    </View>
  );
}

Step 2: Write a jest unit test that covers all three states

The testing strategy for a component with one useState hook is similar to the testing strategy for a component with multiple useState hooks.

In general, you should test the following aspects of a component that uses useState hooks:

  1. Verify that the component renders correctly based on the state.
  2. Verify that the state updates correctly when the component is interacted with.
  3. Verify that the component correctly handles edge cases related to the state.

To test the first aspect, you can render the component in different states and use expect statements to verify that the component’s output matches your expectations. To test the second aspect, you can simulate user interactions (e.g. clicks, key presses) and then use expect statements to verify that the state has been updated correctly. To test the third aspect, you can create test cases that explore edge cases related to the state (e.g. what happens if the state is null or undefined).

When testing a component with multiple useState hooks, you should test each hook separately to ensure that the state updates correctly and that the component renders as expected based on the state. You can use the same testing strategy for each hook as you would for a component with one useState hook.

Overall, the key to testing components that use useState hooks is to ensure that you are testing all aspects of the component’s state management, and that you are testing each hook separately to ensure that the hooks are working correctly together.

Here is an example test for the component:

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

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useState: jest.fn(),
}));

describe('MyComponent', () => {
  it('renders error state', () => {
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);
    // @ts-ignore
    React.useState.mockReturnValueOnce([true, jest.fn()]);

    const wrapper = render(<ExampleComponent />);
    expect(wrapper.getByText('Error!')).toBeDefined();
    expect(wrapper.toJSON()).toMatchSnapshot();
  });

  it('renders default state', () => {
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);

    const wrapper = render(<ExampleComponent />);
    expect(wrapper.getByText('Data Loaded!')).toBeDefined();
    expect(wrapper.toJSON()).toMatchSnapshot();
  });

  it('renders loading state', () => {
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);

    const wrapper = render(<ExampleComponent />);
    expect(wrapper.getByText('Loading...')).toBeDefined();
  });
});

Step 3: Properly Organizing Mock useState Hooks in React Native Tests

When using multiple mock useState hooks in a single test, you should make sure to order them in the same order as they appear in the component code.Here is an example of how to properly organize mock useState hooks in a test for a component that has two useState hooks:

  it('renders error state', () => {
    // @ts-ignore
    React.useState.mockReturnValueOnce([false, jest.fn()]);
    // @ts-ignore
    React.useState.mockReturnValueOnce([true, jest.fn()]);

    const wrapper = render(<ExampleComponent />);
    expect(wrapper.getByText('Error!')).toBeDefined();
    expect(wrapper.toJSON()).toMatchSnapshot();
  });

To mock the first hook, we use mockReturnValueOnce to specify that the hook should return an array with the value false and a mock function. Since isLoading is the first useState hook in the component code, we need to make sure that this mock hook is returned first.

To mock the second hook, we use mockReturnValueOnce again to specify that the hook should return an array with the value true and a mock function. Since hasError is the second useState hook in the component code, we need to make sure that this mock hook is returned second.

By following this order, we can ensure that our tests are correctly simulating the state of the component and that the component is rendering correctly based on that state.

Overall, organizing mock useState hooks in the correct order is an important step in testing components that use hooks. By following the order of the hooks in the component code, we can ensure that our tests are accurately simulating the component’s state and that our tests are covering all possible states of the component.

Step 3: Understanding the snapshots

Jest snapshots are a way to capture the output of a component and compare it to a previous version of the output. When you run your test, Jest will create a snapshot file containing the output of your component for each test case. You can then use these snapshots to ensure that your component is rendering correctly.

In the example code above, we have three test cases, each of which renders the component in a different state. Jest creates a separate snapshot for each test case, which is stored in a snapshot file. The snapshot file contains a description of the component’s output in JSON format.

Each snapshot is represented by a key-value pair in the snapshot file. The key is a description of the test case, and the value is the output of the component for that test case. The value is a string that includes the HTML markup of the rendered component.

For example, the following is a snapshot of the component in the “renders default state” test case:

exports[`MyComponent renders default state 1`] = `
<View>
  <Text>
    Data Loaded!
  </Text>
</View>
`;

The key is “MyComponent renders default state 1”, which describes the test case. The value is the HTML markup of the component’s output, which includes a View element and a Text element with the text “Data Loaded!”.

You can use Jest snapshots to compare the output of your component across different test runs. When you make changes to your component, Jest will compare the new output to the previous snapshot. If the output is different, Jest will alert you that the snapshot has changed, and you can decide whether to accept the change or update the snapshot.

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MyComponent renders default state 1`] = `
<View>
  <Text>
    Data Loaded!
  </Text>
</View>
`;

exports[`MyComponent renders error state 1`] = `
<View>
  <Text>
    Error!
  </Text>
</View>
`;

exports[`MyComponent renders loading state 1`] = `
<View>
  <Text>
    Loading...
  </Text>
</View>
`;

And that’s it! You now know how to test the useState hook in React Native using the testing library.

comments powered by Disqus