Comprehensive guide to testing your React Native applications with Jest, React Native Testing Library, and Detox. Bug-free apps ahead!
Testing is crucial for building reliable React Native apps. Let's dive into a comprehensive testing strategy that will make your apps bulletproof!
Start with testing individual functions and components:
// utils.test.js
import { formatCurrency, validateEmail } from '../utils';
describe('Utility Functions', () => {
test('formatCurrency formats numbers correctly', () => {
expect(formatCurrency(1234.56)).toBe('$1,234.56');
expect(formatCurrency(0)).toBe('$0.00');
});
test('validateEmail validates email addresses', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('invalid-email')).toBe(false);
});
});
Test your React components with React Native Testing Library:
// LoginForm.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import LoginForm from '../LoginForm';
describe('LoginForm', () => {
test('renders login form correctly', () => {
const { getByPlaceholderText, getByText } = render(<LoginForm />);
expect(getByPlaceholderText('Email')).toBeTruthy();
expect(getByPlaceholderText('Password')).toBeTruthy();
expect(getByText('Login')).toBeTruthy();
});
test('calls onLogin with correct credentials', async () => {
const mockOnLogin = jest.fn();
const { getByPlaceholderText, getByText } = render(
<LoginForm onLogin={mockOnLogin} />
);
fireEvent.changeText(getByPlaceholderText('Email'), 'test@example.com');
fireEvent.changeText(getByPlaceholderText('Password'), 'password123');
fireEvent.press(getByText('Login'));
await waitFor(() => {
expect(mockOnLogin).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
});
Test how components work together:
// UserProfile.test.js
import React from 'react';
import { render, waitFor } from '@testing-library/react-native';
import { UserProvider } from '../context/UserContext';
import UserProfile from '../UserProfile';
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
};
const renderWithProvider = (component) => {
return render(
<UserProvider value={{ user: mockUser }}>
{component}
</UserProvider>
);
};
describe('UserProfile Integration', () => {
test('displays user information correctly', async () => {
const { getByText } = renderWithProvider(<UserProfile />);
await waitFor(() => {
expect(getByText('John Doe')).toBeTruthy();
expect(getByText('john@example.com')).toBeTruthy();
});
});
});
Test the complete user journey:
// e2e/login.e2e.js
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should login successfully with valid credentials', async () => {
await element(by.id('email-input')).typeText('test@example.com');
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
await expect(element(by.text('Welcome!'))).toBeVisible();
});
it('should show error with invalid credentials', async () => {
await element(by.id('email-input')).typeText('invalid@example.com');
await element(by.id('password-input')).typeText('wrongpassword');
await element(by.id('login-button')).tap();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
});
Mock API calls and external services:
// __mocks__/api.js
export const authAPI = {
login: jest.fn(() => Promise.resolve({ token: 'mock-token' })),
logout: jest.fn(() => Promise.resolve()),
getProfile: jest.fn(() => Promise.resolve(mockUser)),
};
// In your test file
import { authAPI } from '../api';
jest.mock('../api');
describe('Auth Service', () => {
test('handles login success', async () => {
authAPI.login.mockResolvedValue({ token: 'test-token' });
const result = await login('test@example.com', 'password');
expect(result.token).toBe('test-token');
});
});
Set up coverage reporting:
// package.json
{
"scripts": {
"test:coverage": "jest --coverage"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!src/**/*.test.{js,jsx}",
"!src/index.js"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
Happy testing! Your users will thank you for the bug-free experience! π§ͺ
Help us improve our documentation