# Jest Testing Patterns

## Overview

The mixpanel-react-native library demonstrates sophisticated Jest testing patterns for React Native libraries, including comprehensive mocking strategies, async testing patterns, and integration testing approaches.

## 🎯 Testing Architecture

### Test Organization Strategy

```
__tests__/
├── jest_setup.js              # Global test configuration & mocks
├── core.test.js               # Core functionality tests
├── index.test.js              # Main API surface tests  
├── main.test.js               # JavaScript implementation tests
├── network.test.js            # Network layer tests
└── queue.test.js              # Queue management tests

__mocks__/
└── @react-native-async-storage/
    └── async-storage.js       # AsyncStorage mock
```

**Organization Principles**:
- One test file per major module
- Centralized mocking in `jest_setup.js`
- Mock directory mirrors node_modules structure
- Clear separation of concerns

### Jest Configuration

```javascript
// package.json
"jest": {
  "modulePathIgnorePatterns": [
    "<rootDir>/Samples/"           // Exclude sample apps from tests
  ],
  "testMatch": [
    "<rootDir>/__tests__/*.test.js"  // Explicit test file pattern
  ],
  "setupFiles": [
    "<rootDir>/__tests__/jest_setup.js"  // Global setup
  ],
  "verbose": true,                   // Detailed test output
  "preset": "react-native",          // RN-specific configuration
  "transform": {
    "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
  }
}
```

**Configuration Insights**:
- React Native preset provides essential mocks
- Custom preprocessor for JS transformation
- Verbose output for debugging
- Sample apps excluded to avoid noise

## 🎭 Advanced Mocking Strategies

### 1. Comprehensive Native Module Mocking

```javascript
// __tests__/jest_setup.js
jest.doMock("react-native", () => {
  return Object.setPrototypeOf({
    // Mock the complete native module API
    NativeModules: {
      MixpanelReactNative: {
        // Core methods
        initialize: jest.fn(),
        setServerURL: jest.fn(),
        setLoggingEnabled: jest.fn(),
        
        // Tracking methods
        track: jest.fn(),
        trackWithGroups: jest.fn(),
        
        // Identity methods
        identify: jest.fn(),
        alias: jest.fn(),
        reset: jest.fn(),
        getDistinctId: jest.fn(),
        
        // People Analytics methods
        set: jest.fn(),
        setOnce: jest.fn(),
        increment: jest.fn(),
        append: jest.fn(),
        union: jest.fn(),
        remove: jest.fn(),
        unset: jest.fn(),
        trackCharge: jest.fn(),
        clearCharges: jest.fn(),
        deleteUser: jest.fn(),
        
        // Group Analytics methods
        groupSetProperties: jest.fn(),
        groupSetPropertyOnce: jest.fn(),
        groupUnsetProperty: jest.fn(),
        groupRemovePropertyValue: jest.fn(),
        groupUnionProperty: jest.fn(),
        
        // Configuration methods
        setFlushOnBackground: jest.fn(),
        setUseIpAddressForGeolocation: jest.fn(),
        setFlushBatchSize: jest.fn(),
        hasOptedOutTracking: jest.fn(),
        optInTracking: jest.fn(),
        optOutTracking: jest.fn(),
        
        // Super properties
        registerSuperProperties: jest.fn(),
        registerSuperPropertiesOnce: jest.fn(),
        unregisterSuperProperty: jest.fn(),
        getSuperProperties: jest.fn(),
        clearSuperProperties: jest.fn(),
        
        // Event timing
        timeEvent: jest.fn(),
        eventElapsedTime: jest.fn(),
        
        // Groups
        setGroup: jest.fn(),
        getGroup: jest.fn(),
        addGroup: jest.fn(),
        removeGroup: jest.fn(),
        deleteGroup: jest.fn(),
      },
    },
  }, ReactNative);
});
```

**Mocking Philosophy**:
- **Complete API coverage**: Every native method mocked
- **Consistent interface**: Mocks match actual native API
- **Test isolation**: Each test gets fresh mock state
- **Debugging support**: Verbose method tracking

### 2. External Dependency Mocking

```javascript
// __tests__/jest_setup.js

// UUID generation mocking
jest.mock("uuid", () => ({
  v4: jest.fn(() => "mocked-uuid-12345"),
}));

// Expo crypto mocking with dynamic IDs
jest.mock("expo-crypto", () => ({
  randomUUID: jest.fn(
    () => "mocked-uuid-string-" + Math.random().toString(36).substring(2, 15)
  ),
}));

// AsyncStorage comprehensive mocking
jest.mock("@react-native-async-storage/async-storage", () => ({
  getItem: jest.fn().mockResolvedValue(null),
  setItem: jest.fn().mockResolvedValue(undefined),
  removeItem: jest.fn().mockResolvedValue(undefined),
}));
```

**External Mock Strategies**:
- **Deterministic UUIDs**: Predictable test behavior
- **Async storage**: Promise-based mocks
- **Realistic responses**: Match actual API behavior

### 3. Internal Module Mocking

```javascript
// __tests__/core.test.js

// Mock internal modules with factory functions
jest.mock("mixpanel-react-native/javascript/mixpanel-queue", () => ({
  MixpanelQueueManager: {
    initialize: jest.fn(),
    enqueue: jest.fn(),
    getQueue: jest.fn(() => []),
    spliceQueue: jest.fn(),
    clearQueue: jest.fn(),
  },
}));

jest.mock("mixpanel-react-native/javascript/mixpanel-network", () => ({
  MixpanelNetwork: {
    sendRequest: jest.fn(),
  },
}));

jest.mock("mixpanel-react-native/javascript/mixpanel-config", () => ({
  MixpanelConfig: {
    getInstance: jest.fn().mockReturnValue({
      getFlushInterval: jest.fn().mockReturnValue(1000),
      getFlushBatchSize: jest.fn().mockReturnValue(50),
      getServerURL: jest.fn(),
      getUseIpAddressForGeolocation: jest.fn(),
    }),
  },
}));
```

**Internal Mock Benefits**:
- **Unit test isolation**: Test one module at a time
- **Fast execution**: No real network or storage I/O
- **Predictable behavior**: Controlled responses
- **Error simulation**: Easy to test error conditions

## ✅ Test Patterns & Best Practices

### 1. Async Testing with Promises

```javascript
// Example from network.test.js (inferred pattern)
describe('MixpanelNetwork', () => {
  beforeEach(() => {
    // Reset all mocks before each test
    jest.clearAllMocks();
  });

  it('should handle successful requests', async () => {
    // Setup
    const mockResponse = { status: 200, json: () => Promise.resolve(1) };
    global.fetch = jest.fn().mockResolvedValue(mockResponse);

    // Execute
    await MixpanelNetwork.sendRequest({
      token: 'test-token',
      endpoint: '/track/',
      data: [{ event: 'test' }],
      serverURL: 'https://api.mixpanel.com',
      useIPAddressForGeoLocation: true
    });

    // Verify
    expect(global.fetch).toHaveBeenCalledWith(
      'https://api.mixpanel.com/track/?ip=1',
      expect.objectContaining({
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
      })
    );
  });

  it('should retry on failure with exponential backoff', async () => {
    // Setup - simulate network failure then success
    global.fetch = jest.fn()
      .mockRejectedValueOnce(new Error('Network error'))
      .mockResolvedValueOnce({ status: 200, json: () => Promise.resolve(1) });

    // Execute
    await MixpanelNetwork.sendRequest({...});

    // Verify retry behavior
    expect(global.fetch).toHaveBeenCalledTimes(2);
  });
});
```

### 2. State Testing Patterns

```javascript
// Example from queue.test.js (inferred pattern)
describe('MixpanelQueueManager', () => {
  beforeEach(() => {
    // Reset internal state
    MixpanelQueueManager._queues = {};
  });

  it('should maintain separate queues per token', async () => {
    const token1 = 'token1';
    const token2 = 'token2';
    const eventData = { event: 'test' };

    // Initialize queues
    await MixpanelQueueManager.initialize(token1, MixpanelType.EVENTS);
    await MixpanelQueueManager.initialize(token2, MixpanelType.EVENTS);

    // Add events to different tokens
    await MixpanelQueueManager.enqueue(token1, MixpanelType.EVENTS, eventData);
    await MixpanelQueueManager.enqueue(token2, MixpanelType.EVENTS, eventData);

    // Verify isolation
    const queue1 = MixpanelQueueManager.getQueue(token1, MixpanelType.EVENTS);
    const queue2 = MixpanelQueueManager.getQueue(token2, MixpanelType.EVENTS);

    expect(queue1).toHaveLength(1);
    expect(queue2).toHaveLength(1);
    expect(queue1[0]).toBe(eventData);
    expect(queue2[0]).toBe(eventData);
  });
});
```

### 3. Error Handling Testing

```javascript
// Example error testing pattern
describe('Error Handling', () => {
  it('should throw descriptive errors for invalid inputs', () => {
    expect(() => {
      mixpanel.track('', {}); // Empty event name
    }).toThrow('eventName is not a valid string');

    expect(() => {
      mixpanel.track('event', 'invalid'); // Non-object properties
    }).toThrow('properties is not a valid json object');
  });

  it('should handle storage failures gracefully', async () => {
    // Mock storage failure
    AsyncStorage.setItem.mockRejectedValue(new Error('Storage full'));

    // Should not throw, but log error
    await expect(
      MixpanelPersistent.getInstance().persistSuperProperties('token', {})
    ).resolves.toBeUndefined();

    expect(console.error).toHaveBeenCalledWith(
      expect.stringContaining('error setting item in storage')
    );
  });
});
```

## 🔧 Testing Utilities

### 1. Mock Factory Pattern

```javascript
// __tests__/jest_setup.js
const createMockStorage = () => ({
  AsyncStorageAdapter: jest.fn().mockImplementation(() => ({
    getItem: jest.fn().mockResolvedValue(null),
    setItem: jest.fn().mockResolvedValue(undefined),
    removeItem: jest.fn().mockResolvedValue(undefined),
  })),
});

jest.mock("mixpanel-react-native/javascript/mixpanel-storage", createMockStorage);
```

### 2. Test Data Factories

```javascript
// Example test data patterns
const createTestEvent = (overrides = {}) => ({
  event: 'Test Event',
  properties: { testProp: 'testValue' },
  token: 'test-token',
  distinct_id: 'test-user',
  time: Date.now(),
  ...overrides
});

const createTestUser = (overrides = {}) => ({
  $distinct_id: 'test-user',
  $set: { name: 'Test User' },
  $token: 'test-token',
  ...overrides
});
```

## 📊 Test Coverage Strategy

### Core Functionality Coverage
- ✅ **API validation**: All input validation paths
- ✅ **Mode switching**: Native vs JavaScript fallback
- ✅ **Queue management**: Enqueue, batch processing, persistence
- ✅ **Network handling**: Success, failure, retry logic
- ✅ **State management**: Identity, super properties, configuration
- ✅ **Error scenarios**: Invalid inputs, storage failures, network errors

### Integration Testing Approach
```javascript
// Example integration test pattern
describe('End-to-End Tracking Flow', () => {
  it('should track event through complete pipeline', async () => {
    // Setup complete system
    const mixpanel = new Mixpanel('test-token', true, false);
    await mixpanel.init();

    // Execute tracking
    mixpanel.track('Purchase', { amount: 99.99 });

    // Verify pipeline steps
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Purchase',
        properties: expect.objectContaining({ amount: 99.99 })
      })
    );
  });
});
```

## 🚀 Performance Testing Patterns

### 1. Batch Processing Tests

```javascript
describe('Batch Processing Performance', () => {
  it('should process large queues efficiently', async () => {
    const events = Array(1000).fill().map((_, i) => ({ event: `Event${i}` }));
    
    // Add all events
    for (const event of events) {
      await MixpanelQueueManager.enqueue('token', MixpanelType.EVENTS, event);
    }

    // Process in batches
    const startTime = Date.now();
    await MixpanelCore.flush('token');
    const endTime = Date.now();

    // Verify performance
    expect(endTime - startTime).toBeLessThan(1000); // Under 1 second
  });
});
```

### 2. Memory Leak Testing

```javascript
describe('Memory Management', () => {
  it('should cleanup resources on reset', async () => {
    // Add data
    await mixpanel.track('event', {});
    await mixpanel.registerSuperProperties({ prop: 'value' });

    // Reset
    await mixpanel.reset();

    // Verify cleanup
    expect(await mixpanel.getSuperProperties()).toEqual({});
    expect(MixpanelQueueManager.getQueue('token', MixpanelType.EVENTS)).toEqual([]);
  });
});
```

## 📋 Jest Best Practices Discovered

### 1. **Comprehensive Mocking**
Mock all external dependencies completely to ensure test isolation

### 2. **Async Testing**
Use proper async/await patterns for testing Promise-based APIs

### 3. **State Reset**
Clear all mocks and reset state between tests

### 4. **Error Path Testing**
Test both success and failure scenarios thoroughly

### 5. **Integration Testing**
Test module interactions, not just individual units

### 6. **Performance Awareness**
Include basic performance assertions in tests

### 7. **Realistic Mocks**
Mocks should behave like real implementations

### 8. **Test Organization**
Group related tests and use descriptive names