Component Testing in React Native Using Jest
Introduction
Component testing ensures that individual components in a React Native application function as expected. In this documentation, we will cover setting up Jest for component testing, writing test cases, and executing tests.
Installation
To enable component testing, install the required dependencies:
npm i jest-expo jest react-test-renderer jest-html-reporter
Setup
Modify your package.json to include the following scripts:
"scripts": {
"test": "jest",
"test:report": "jest --reporters",
"test:coverage": "jest --coverage"
}
Configure Jest by adding the following configuration in devDependencies in package.json:
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg)"
],
"moduleNameMapper": {
"\\.svg$": "<rootDir>/__mocks__/svgMock.js"
},
"reporters": [
"default",
[
"./node_modules/jest-html-reporter",
{
"pageTitle": "Test Report"
}
]
]
}
Mocking Dependencies
Create a __mocks__ folder in the root of your project. Inside this folder, create a file named svgMock.js and add the following code:
/* eslint-disable no-undef */
module.exports = 'SvgMock';
This ensures that Jest can handle .svg imports properly.
Writing Test Cases
Create a __tests__ folder in the project root and add test files inside it. Below is an example test for a CustomTextField component.
Component Implementation (CustomTextField.tsx)
import React, { useState } from "react";
import { TextInput, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native";
import GreenCheck from "../assets/images/GreenCheck.svg";
import { Colors } from "../styles/Colors";
export interface CustomTextFieldProps {
label: string;
isMandatory: boolean;
placeholder: string;
value: string;
onChangeText: (text: string) => void;
errorMessage?: string;
disabled?: boolean;
isButtonVisible: boolean;
isButtonLoading?: boolean;
isVerified?: boolean;
buttonText?: string;
onPress?: () => void;
testID: string;
verifyTestID?: string;
mainViewTestID?: string;
}
export const CustomTextField: React.FC<CustomTextFieldProps> = ({
label,
isMandatory,
placeholder,
value,
onChangeText,
errorMessage,
disabled = false,
isButtonVisible = false,
isButtonLoading = false,
buttonText = "Change",
onPress,
isVerified = false,
testID,
verifyTestID,
mainViewTestID,
}) => {
return (
<View style={{ marginBottom: 10 }}>
<Text>{label} {isMandatory && <Text>*</Text>}</Text>
<View testID={mainViewTestID}>
<TextInput
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
editable={!disabled}
testID={testID}
/>
{value.length > 0 && isVerified && !isButtonVisible && <GreenCheck height={24} width={24} />}
{value.length > 0 && isButtonVisible && (
isButtonLoading ? <ActivityIndicator size="small" color={Colors.primary} /> :
<TouchableOpacity onPress={onPress} testID={verifyTestID}>
<Text>{buttonText}</Text>
</TouchableOpacity>
)}
</View>
{errorMessage && <Text>{errorMessage}</Text>}
</View>
);
};
Writing Tests (TextField.test.tsx)
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { CustomTextField, CustomTextFieldProps } from '../CustomTextField';
const nameProps: CustomTextFieldProps = {
label: 'First Name',
isMandatory: true,
placeholder: 'Enter first name',
value: '',
onChangeText: jest.fn(),
isButtonVisible: false,
testID: 'custom-textfield',
};
describe('CustomTextField', () => {
it('renders label and placeholder correctly', () => {
const { getByText, getByPlaceholderText } = render(<CustomTextField {...nameProps} />);
expect(getByText('First Name')).toBeTruthy();
expect(getByPlaceholderText('Enter first name')).toBeTruthy();
});
it('handles text input correctly', () => {
const onChangeText = jest.fn();
const { getByTestId } = render(<CustomTextField {...nameProps} onChangeText={onChangeText} />);
const input = getByTestId('custom-textfield');
fireEvent.changeText(input, 'John Doe');
expect(onChangeText).toHaveBeenCalledWith('John Doe');
});
it('shows error message when provided', () => {
const { getByText } = render(
<CustomTextField {...nameProps} errorMessage="This is an error" />
);
expect(getByText('This is an error')).toBeTruthy();
});
it('shows verify button when isButtonVisible is true', () => {
const onPress = jest.fn();
const { getByText } = render(
<CustomTextField {...nameProps} value="test" isButtonVisible buttonText="Verify" onPress={onPress} />
);
const button = getByText('Verify');
fireEvent.press(button);
expect(onPress).toHaveBeenCalled();
});
});
Understanding describe and it
describe(name, fn): A block that groups related test cases.it(name, fn): Defines an individual test case.
Example:
describe('ComponentName', () => {
it('should render correctly', () => {
// Test logic
});
});
Using AmazonQ for test generation
You can use Amazon Q to generate test cases automatically. Simply install the Amazon Q extension in VS Code and use the following command in chat:
/test filename.tsx
This will generate test cases for the specified file, making test writing faster and more efficient.
This ensures modular and organized test structures for better readability and maintainability.
Running Tests
To run the tests, use the following command:
npm test
To generate a coverage report:
npm run test:coverage
To generate a test report:
npm run test:report
Conclusion
This documentation covered setting up Jest for testing, writing test cases for a React Native component, and executing tests. Proper component testing ensures the reliability and functionality of individual components in a React Native project.