Files
self/docs/testing-guide.md
Justin Hernandez b2839e1633 Chore: improve ai context (#883)
* add gigamind context

* updates

* update ai context

* doc updates

* add migration context

* docs: refine commit checks and mobile app instructions (#884)

* docs: refine commit checks and add mobile app guidelines

* cr feedback

* quality updates

* format md file

* updates

* future proof tests
2025-08-12 16:39:15 -07:00

7.3 KiB

Testing Guide

Jest Configuration

Setup Files

The project uses comprehensive Jest configuration with:

  • jest.setup.js - Contains mocks for all native modules
  • jest.config.cjs - Configures transform patterns and module mapping
  • tsconfig.test.json - Test-specific TypeScript configuration

Module Mapping

moduleNameMapper: {
  '^@env$': '<rootDir>/tests/__setup__/@env.js',
  '\\.svg$': '<rootDir>/tests/__setup__/svgMock.js',
  '^@/(.*)$': '<rootDir>/src/$1',
  '^@tests/(.*)$': '<rootDir>/tests/src/$1',
}

Transform Patterns

transformIgnorePatterns: [
  'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase)/)',
]

Mock Patterns

Native Module Mocks

All React Native native modules are mocked in jest.setup.js:

Firebase Mocks

jest.mock('@react-native-firebase/messaging', () => {
  return () => ({
    hasPermission: jest.fn(() => Promise.resolve(true)),
    requestPermission: jest.fn(() => Promise.resolve(true)),
    getToken: jest.fn(() => Promise.resolve('mock-token')),
    onMessage: jest.fn(() => jest.fn()),
    onNotificationOpenedApp: jest.fn(() => jest.fn()),
    getInitialNotification: jest.fn(() => Promise.resolve(null)),
    setBackgroundMessageHandler: jest.fn(),
    registerDeviceForRemoteMessages: jest.fn(() => Promise.resolve()),
    subscribeToTopic: jest.fn(),
    unsubscribeFromTopic: jest.fn(),
  });
});

Keychain Mocks

jest.mock('react-native-keychain', () => ({
  SECURITY_LEVEL_ANY: 'MOCK_SECURITY_LEVEL_ANY',
  SECURITY_LEVEL_SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
  SECURITY_LEVEL_SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
  setGenericPassword: jest.fn(),
  getGenericPassword: jest.fn(),
  resetGenericPassword: jest.fn(),
  ACCESSIBLE: {
    WHEN_UNLOCKED: 'AccessibleWhenUnlocked',
    AFTER_FIRST_UNLOCK: 'AccessibleAfterFirstUnlock',
    ALWAYS: 'AccessibleAlways',
    WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: 'AccessibleWhenPasscodeSetThisDeviceOnly',
    WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'AccessibleWhenUnlockedThisDeviceOnly',
    AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 'AccessibleAfterFirstUnlockThisDeviceOnly',
    ALWAYS_THIS_DEVICE_ONLY: 'AccessibleAlwaysThisDeviceOnly',
  },
  ACCESS_CONTROL: {
    USER_PRESENCE: 'UserPresence',
    BIOMETRY_ANY: 'BiometryAny',
    BIOMETRY_CURRENT_SET: 'BiometryCurrentSet',
    DEVICE_PASSCODE: 'DevicePasscode',
    APPLICATION_PASSWORD: 'ApplicationPassword',
    BIOMETRY_ANY_OR_DEVICE_PASSCODE: 'BiometryAnyOrDevicePasscode',
    BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE: 'BiometryCurrentSetOrDevicePasscode',
  },
}));

NFC Mocks

jest.mock('react-native-nfc-manager', () => ({
  start: jest.fn(),
  isSupported: jest.fn().mockResolvedValue(true),
  isEnabled: jest.fn().mockResolvedValue(true),
  registerTagEvent: jest.fn(),
  unregisterTagEvent: jest.fn(),
  requestTechnology: jest.fn(),
  cancelTechnologyRequest: jest.fn(),
  getTag: jest.fn(),
  setAlertMessage: jest.fn(),
  sendMifareCommand: jest.fn(),
  sendCommandAPDU: jest.fn(),
  transceive: jest.fn(),
  getMaxTransceiveLength: jest.fn(),
  setTimeout: jest.fn(),
  connect: jest.fn(),
  close: jest.fn(),
  cleanUpTag: jest.fn(),
  default: {
    // Same methods as above
  },
}));

Database Testing

SQLite operations are mocked for testing:

// Mock react-native-sqlite-storage
jest.mock('react-native-sqlite-storage', () => ({
  enablePromise: jest.fn(),
  openDatabase: jest.fn(),
}));

// Test database instance
const mockDb = {
  executeSql: jest.fn(() => Promise.resolve()),
};

mockSQLite.openDatabase.mockResolvedValue(mockDb);

Test Organization

File Structure

tests/
├── __setup__/           # Global test setup and mocks
├── src/                 # Unit tests mirroring source structure
├── integration/         # Integration tests
├── e2e/                # End-to-end tests
└── utils/              # Test utilities and helpers

Test File Naming

  • Unit tests: *.test.ts or *.test.tsx
  • Integration tests: *.integration.test.ts
  • E2E tests: Platform-specific YAML files

Testing Patterns

Hook Testing

Use renderHook for testing custom hooks:

import { renderHook } from '@testing-library/react-native';

describe('useModal', () => {
  it('should return modal functions', () => {
    const { result } = renderHook(() => useModal(mockParams));

    expect(result.current).toHaveProperty('showModal');
    expect(result.current).toHaveProperty('dismissModal');
    expect(result.current).toHaveProperty('visible');
  });
});

Component Testing

Test React Native components with proper mocking:

import { render, fireEvent } from '@testing-library/react-native';

describe('Component', () => {
  it('should handle user interactions', () => {
    const mockNavigation = { navigate: jest.fn() };
    const { getByText } = render(<Component navigation={mockNavigation} />);

    fireEvent.press(getByText('Button'));
    expect(mockNavigation.navigate).toHaveBeenCalledWith('Screen');
  });
});

Error Testing

Test error boundaries and error handling:

describe('Error handling', () => {
  beforeEach(() => {
    // Suppress console.error during tests
    jest.spyOn(console, 'error').mockImplementation(() => {});
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should handle errors gracefully', () => {
    // Test error scenarios
  });
});

E2E Testing

Platform-Specific Flows

  • Separate test files for iOS and Android
  • Maestro for cross-platform E2E testing
  • Platform-specific build commands before E2E tests

Test Commands

# iOS E2E
yarn test:e2e:ios

# Android E2E
yarn test:e2e:android

Test Data Management

  • Mock passport data for testing
  • Test-specific environment variables
  • Cleanup between test runs

Performance Testing

Bundle Analysis

# Analyze bundle size
yarn analyze:bundle:ios
yarn analyze:bundle:android

# Tree shaking analysis
yarn analyze:tree-shaking

Memory Testing

  • Memory leak detection in tests
  • Component lifecycle testing
  • Native module cleanup verification

Coverage Strategy

Coverage Commands

# Basic coverage
yarn test:coverage

# CI coverage with multiple formats
yarn test:coverage:ci

Coverage Configuration

  • Jest coverage reporting with multiple formats
  • CI-specific coverage commands
  • Coverage thresholds for critical paths

Best Practices

Test Isolation

  • Always clean up mocks between tests
  • Use beforeEach and afterEach for setup/cleanup
  • Avoid shared state between tests

Mock Management

  • Mock at the right level (module vs function)
  • Provide realistic mock return values
  • Test error scenarios with mocks

Async Testing

  • Use async/await for async operations
  • Test both success and failure paths
  • Handle promises properly in tests

Platform Testing

  • Test platform-specific code paths
  • Mock platform-specific modules
  • Test conditional rendering logic