Unit Testing with Jest
Learn to write unit tests for React Native components and functions using Jest testing framework
60 min•By Priygop Team•Last updated: Feb 2026
Jest Setup
Set up Jest testing environment for React Native applications with proper configuration and mocking capabilities.
Jest Configuration
- Install Jest and React Native testing dependencies
- Configure jest.config.js for React Native
- Set up test environment and presets
- Configure module name mapping
- Set up coverage reporting
- Configure test timeout and setup files
Writing Tests
Write comprehensive unit tests for components, functions, and utilities with proper assertions and test coverage.
comprehensive Test Example
Example
// utils/helpers.js
export const formatCurrency = (amount, currency = 'USD') => {
if (typeof amount !== 'number') {
throw new Error('Amount must be a number');
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount);
};
export const validateEmail = (email) => {
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
return emailRegex.test(email);
};
export const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
};
// components/Button.js
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
export const Button = ({ title, onPress, disabled = false, variant = 'primary' }) => {
return (
<TouchableOpacity
style={[styles.button, styles[variant], disabled && styles.disabled]}
onPress={onPress}
disabled={disabled}
testID="button"
>
<Text style={[styles.text, disabled && styles.disabledText]}>
{title}
</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
primary: {
backgroundColor: '#2196F3',
},
secondary: {
backgroundColor: '#6c757d',
},
disabled: {
backgroundColor: '#e9ecef',
},
text: {
color: 'white',
fontWeight: 'bold',
},
disabledText: {
color: '#6c757d',
},
});
// __tests__/utils/helpers.test.js
import { formatCurrency, validateEmail, debounce } from '../utils/helpers';
describe('formatCurrency', () => {
it('should format currency correctly for USD', () => {
expect(formatCurrency(100)).toBe('$100.00');
expect(formatCurrency(1000)).toBe('$1,000.00');
expect(formatCurrency(99.99)).toBe('$99.99');
});
it('should format currency for different currencies', () => {
expect(formatCurrency(100, 'EUR')).toBe('€100.00');
expect(formatCurrency(100, 'GBP')).toBe('£100.00');
});
it('should throw error for invalid input', () => {
expect(() => formatCurrency('invalid')).toThrow('Amount must be a number');
expect(() => formatCurrency(null)).toThrow('Amount must be a number');
expect(() => formatCurrency(undefined)).toThrow('Amount must be a number');
});
it('should handle edge cases', () => {
expect(formatCurrency(0)).toBe('$0.00');
expect(formatCurrency(-100)).toBe('-$100.00');
});
});
describe('validateEmail', () => {
it('should validate correct email addresses', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('user.name@domain.co.uk')).toBe(true);
expect(validateEmail('user+tag@example.org')).toBe(true);
});
it('should reject invalid email addresses', () => {
expect(validateEmail('invalid-email')).toBe(false);
expect(validateEmail('@example.com')).toBe(false);
expect(validateEmail('test@')).toBe(false);
expect(validateEmail('')).toBe(false);
expect(validateEmail(null)).toBe(false);
expect(validateEmail(undefined)).toBe(false);
});
});
describe('debounce', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('should debounce function calls', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 100);
debouncedFn('arg1');
debouncedFn('arg2');
debouncedFn('arg3');
expect(mockFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(100);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith('arg3');
});
it('should reset timer on new calls', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 100);
debouncedFn('first');
jest.advanceTimersByTime(50);
debouncedFn('second');
jest.advanceTimersByTime(50);
expect(mockFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(50);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith('second');
});
});
// __tests__/components/Button.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '../components/Button';
describe('Button Component', () => {
it('should render correctly with title', () => {
const { getByText, getByTestId } = render(
<Button title="Test Button" onPress={() => {}} />
);
expect(getByText('Test Button')).toBeTruthy();
expect(getByTestId('button')).toBeTruthy();
});
it('should call onPress when pressed', () => {
const mockOnPress = jest.fn();
const { getByTestId } = render(
<Button title="Test Button" onPress={mockOnPress} />
);
fireEvent.press(getByTestId('button'));
expect(mockOnPress).toHaveBeenCalledTimes(1);
});
it('should not call onPress when disabled', () => {
const mockOnPress = jest.fn();
const { getByTestId } = render(
<Button title="Test Button" onPress={mockOnPress} disabled={true} />
);
fireEvent.press(getByTestId('button'));
expect(mockOnPress).not.toHaveBeenCalled();
});
it('should apply correct styles for different variants', () => {
const { getByTestId, rerender } = render(
<Button title="Primary" onPress={() => {}} variant="primary" />
);
let button = getByTestId('button');
expect(button.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: '#2196F3' })
);
rerender(<Button title="Secondary" onPress={() => {}} variant="secondary" />);
button = getByTestId('button');
expect(button.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: '#6c757d' })
);
});
it('should apply disabled styles when disabled', () => {
const { getByTestId } = render(
<Button title="Disabled" onPress={() => {}} disabled={true} />
);
const button = getByTestId('button');
expect(button.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: '#e9ecef' })
);
});
it('should handle missing onPress gracefully', () => {
const { getByTestId } = render(
<Button title="No Press" />
);
expect(() => {
fireEvent.press(getByTestId('button'));
}).not.toThrow();
});
});
// jest.config.js
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|react-native-.*|@react-navigation|@react-native-community|@react-native-picker|@react-native-async-storage|@react-native-vector-icons)/)',
],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/__tests__/**',
'!src/**/node_modules/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
testEnvironment: 'jsdom',
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};
// jest.setup.js
import 'react-native-gesture-handler/jestSetup';
// Mock react-native-reanimated
jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');
Reanimated.default.call = () => {};
return Reanimated;
});
// Mock react-native-vector-icons
jest.mock('react-native-vector-icons/MaterialIcons', () => 'Icon');
// Silence the warning: Animated: `useNativeDriver` is not supported
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');