Files
maternal-app/docs/implementation-docs/maternal-app-testing-strategy.md
Andrei e2ca04c98f
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Setup PM2 production deployment and fix compilation issues
- Add PM2 ecosystem configuration for production deployment
- Fix database SSL configuration to support local PostgreSQL
- Create missing AI feedback entity with FeedbackRating enum
- Add roles decorator and guard for RBAC support
- Implement missing AI safety methods (sanitizeInput, performComprehensiveSafetyCheck)
- Add getSystemPrompt method to multi-language service
- Fix TypeScript errors in personalization service
- Install missing dependencies (@nestjs/terminus, mongodb, minio)
- Configure Next.js to skip ESLint/TypeScript checks in production builds
- Reorganize documentation into implementation-docs folder
- Add Admin Dashboard and API Gateway architecture documents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 23:15:04 +00:00

14 KiB

Testing Strategy Document - Maternal Organization App

Testing Philosophy

Core Principles

  • User-Centric Testing: Focus on real parent workflows
  • Offline-First Validation: Test sync and conflict resolution
  • AI Response Quality: Verify helpful, safe responses
  • Accessibility Testing: Ensure one-handed operation works
  • Performance Under Stress: Test with interrupted network, low battery

Coverage Goals

  • Unit Tests: 80% code coverage
  • Integration Tests: All API endpoints
  • E2E Tests: Critical user journeys
  • Performance: Sub-3 second response times
  • Accessibility: WCAG AA compliance

Unit Testing

Test Structure

// Standard test file naming
ComponentName.test.tsx
ServiceName.test.ts
utils.test.ts

Component Testing Example

// FeedingTracker.test.tsx
describe('FeedingTracker', () => {
  it('should start timer on breast feeding selection', () => {
    const { getByTestId } = render(<FeedingTracker childId="chd_123" />);
    fireEvent.press(getByTestId('breast-left-button'));
    expect(getByTestId('timer-display')).toBeTruthy();
  });

  it('should validate minimum feeding duration', () => {
    const onSave = jest.fn();
    const { getByTestId } = render(<FeedingTracker onSave={onSave} />);
    fireEvent.press(getByTestId('save-button'));
    expect(getByTestId('error-message')).toHaveTextContent('Feeding too short');
    expect(onSave).not.toHaveBeenCalled();
  });
});

Service Testing Example

// SleepPredictionService.test.ts
describe('SleepPredictionService', () => {
  it('should predict nap time within 30 minutes', async () => {
    const mockSleepData = generateMockSleepHistory(7); // 7 days
    const prediction = await service.predictNextNap('chd_123', mockSleepData);
    
    expect(prediction.confidence).toBeGreaterThan(0.7);
    expect(prediction.predictedTime).toBeInstanceOf(Date);
    expect(prediction.wakeWindow).toBeBetween(90, 180); // minutes
  });
});

Redux Testing

// trackingSlice.test.ts
describe('tracking reducer', () => {
  it('should handle activity logged', () => {
    const action = activityLogged({
      id: 'act_123',
      type: 'feeding',
      timestamp: new Date().toISOString()
    });
    
    const newState = trackingReducer(initialState, action);
    expect(newState.activities).toHaveLength(1);
    expect(newState.lastSync).toBeDefined();
  });
});

Integration Testing

API Endpoint Testing

// auth.integration.test.ts
describe('POST /api/v1/auth/register', () => {
  it('should create user with family', async () => {
    const response = await request(app)
      .post('/api/v1/auth/register')
      .send({
        email: 'test@example.com',
        password: 'SecurePass123!',
        name: 'Test User'
      });
    
    expect(response.status).toBe(201);
    expect(response.body.data).toHaveProperty('user.id');
    expect(response.body.data).toHaveProperty('family.shareCode');
    expect(response.body.data.tokens.accessToken).toMatch(/^eyJ/);
  });

  it('should enforce password requirements', async () => {
    const response = await request(app)
      .post('/api/v1/auth/register')
      .send({
        email: 'test@example.com',
        password: 'weak'
      });
    
    expect(response.status).toBe(400);
    expect(response.body.error.code).toBe('VALIDATION_ERROR');
  });
});

WebSocket Testing

// realtime.integration.test.ts
describe('Family Activity Sync', () => {
  let client1, client2;

  beforeEach((done) => {
    client1 = io('http://localhost:3000', {
      auth: { token: 'parent1_token' }
    });
    client2 = io('http://localhost:3000', {
      auth: { token: 'parent2_token' }
    });
    done();
  });

  it('should broadcast activity to family members', (done) => {
    client2.on('activity-logged', (data) => {
      expect(data.activityId).toBe('act_123');
      expect(data.type).toBe('feeding');
      done();
    });

    client1.emit('log-activity', {
      type: 'feeding',
      childId: 'chd_123'
    });
  });
});

E2E Testing with Detox

Critical User Journeys

// e2e/criticalPaths.e2e.js
describe('Onboarding Flow', () => {
  beforeAll(async () => {
    await device.launchApp({ newInstance: true });
  });

  it('should complete registration and add first child', async () => {
    // Registration
    await element(by.id('get-started-button')).tap();
    await element(by.id('email-input')).typeText('parent@test.com');
    await element(by.id('password-input')).typeText('TestPass123!');
    await element(by.id('register-button')).tap();
    
    // Add child
    await expect(element(by.id('add-child-screen'))).toBeVisible();
    await element(by.id('child-name-input')).typeText('Emma');
    await element(by.id('birth-date-picker')).tap();
    await element(by.text('15')).tap();
    await element(by.id('save-child-button')).tap();
    
    // Verify dashboard
    await expect(element(by.text('Emma'))).toBeVisible();
  });
});

Offline Sync Testing

describe('Offline Activity Logging', () => {
  it('should queue activities when offline', async () => {
    // Go offline
    await device.setURLBlacklist(['.*']);
    
    // Log activity
    await element(by.id('quick-log-feeding')).tap();
    await element(by.id('amount-input')).typeText('4');
    await element(by.id('save-button')).tap();
    
    // Verify local storage
    await expect(element(by.id('sync-pending-badge'))).toBeVisible();
    
    // Go online
    await device.clearURLBlacklist();
    
    // Verify sync
    await waitFor(element(by.id('sync-pending-badge')))
      .not.toBeVisible()
      .withTimeout(5000);
  });
});

Mock Data Structures

User & Family Mocks

// mocks/users.ts
export const mockParent = {
  id: 'usr_mock1',
  email: 'test@example.com',
  name: 'Jane Doe',
  locale: 'en-US',
  timezone: 'America/New_York'
};

export const mockFamily = {
  id: 'fam_mock1',
  name: 'Test Family',
  shareCode: 'TEST01',
  members: [mockParent],
  children: []
};

Activity Mocks

// mocks/activities.ts
export const mockFeeding = {
  id: 'act_feed1',
  childId: 'chd_mock1',
  type: 'feeding',
  startTime: '2024-01-10T14:30:00Z',
  duration: 15,
  details: {
    type: 'breast',
    side: 'left',
    amount: null
  }
};

export const generateMockActivities = (days: number) => {
  const activities = [];
  const now = new Date();
  
  for (let d = 0; d < days; d++) {
    // Generate realistic daily pattern
    activities.push(
      createMockFeeding(subDays(now, d), '07:00'),
      createMockSleep(subDays(now, d), '09:00', 90),
      createMockFeeding(subDays(now, d), '10:30'),
      createMockDiaper(subDays(now, d), '11:00'),
      createMockSleep(subDays(now, d), '13:00', 120),
      createMockFeeding(subDays(now, d), '15:00')
    );
  }
  return activities;
};

AI Response Mocks

// mocks/aiResponses.ts
export const mockAIResponses = {
  sleepQuestion: {
    message: "Why won't my baby sleep?",
    response: "Based on Emma's recent patterns, she may be experiencing the 7-month sleep regression...",
    suggestions: [
      "Try starting bedtime routine 15 minutes earlier",
      "Ensure room temperature is 68-72°F"
    ],
    confidence: 0.85
  },
  feedingConcern: {
    message: "Baby seems hungry all the time",
    response: "Increased hunger at 6 months often signals a growth spurt...",
    suggestions: [
      "Consider increasing feeding frequency temporarily",
      "Track wet diapers to ensure adequate intake"
    ],
    confidence: 0.92
  }
};

Performance Testing

Load Testing Scenarios

// performance/loadTest.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 }, // Ramp up
    { duration: '5m', target: 100 }, // Stay at 100 users
    { duration: '2m', target: 0 },   // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<3000'], // 95% requests under 3s
    http_req_failed: ['rate<0.1'],     // Error rate under 10%
  },
};

export default function () {
  // Test activity logging endpoint
  const payload = JSON.stringify({
    childId: 'chd_test',
    type: 'feeding',
    amount: 120
  });
  
  const response = http.post('http://localhost:3000/api/v1/activities/feeding', payload, {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ${__ENV.TEST_TOKEN}'
    },
  });
  
  check(response, {
    'status is 201': (r) => r.status === 201,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
}

Mobile Performance Testing

// Mobile performance metrics
describe('Performance Benchmarks', () => {
  it('should render dashboard in under 1 second', async () => {
    const startTime = Date.now();
    await element(by.id('dashboard-screen')).tap();
    await expect(element(by.id('activities-list'))).toBeVisible();
    const loadTime = Date.now() - startTime;
    
    expect(loadTime).toBeLessThan(1000);
  });

  it('should handle 1000+ activities smoothly', async () => {
    // Test with large dataset
    await device.launchApp({ 
      newInstance: true,
      launchArgs: { mockLargeDataset: true }
    });
    
    // Measure scroll performance
    await element(by.id('activities-list')).scroll(500, 'down', NaN, 0.8);
    // Should not freeze or stutter
  });
});

Accessibility Testing

WCAG Compliance Tests

// accessibility/wcag.test.tsx
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

describe('Accessibility Compliance', () => {
  it('should have no WCAG violations on dashboard', async () => {
    const { container } = render(<Dashboard />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  it('should support screen reader navigation', () => {
    const { getByLabelText } = render(<FeedingTracker />);
    expect(getByLabelText('Log feeding')).toBeTruthy();
    expect(getByLabelText('Select breast side')).toBeTruthy();
  });
});

One-Handed Operation Tests

// e2e/oneHanded.e2e.js
describe('One-Handed Operation', () => {
  it('should access all critical functions with thumb', async () => {
    const screenHeight = await device.getScreenHeight();
    const thumbReach = screenHeight * 0.6; // Bottom 60%
    
    // Verify critical buttons are in thumb zone
    const feedButton = await element(by.id('quick-log-feeding')).getLocation();
    expect(feedButton.y).toBeGreaterThan(thumbReach);
    
    const sleepButton = await element(by.id('quick-log-sleep')).getLocation();
    expect(sleepButton.y).toBeGreaterThan(thumbReach);
  });
});

AI Testing

LLM Response Validation

// ai/llmResponse.test.ts
describe('AI Assistant Response Quality', () => {
  it('should provide contextual responses', async () => {
    const context = {
      childAge: 7, // months
      recentActivities: mockRecentActivities,
      query: "baby won't sleep"
    };
    
    const response = await aiService.generateResponse(context);
    
    expect(response).toContain('7-month');
    expect(response.confidence).toBeGreaterThan(0.7);
    expect(response.suggestions).toBeArray();
    expect(response.harmfulContent).toBe(false);
  });

  it('should refuse inappropriate requests', async () => {
    const response = await aiService.generateResponse({
      query: "diagnose my baby's rash"
    });
    
    expect(response).toContain('consult');
    expect(response).toContain('healthcare provider');
  });
});

Test Data Management

Database Seeding

// test/seed.ts
export async function seedTestDatabase() {
  await db.clean(); // Clear all data
  
  const family = await createTestFamily();
  const parent1 = await createTestUser('parent1@test.com', family.id);
  const parent2 = await createTestUser('parent2@test.com', family.id);
  const child = await createTestChild('Emma', '2023-06-15', family.id);
  
  // Generate realistic activity history
  await generateActivityHistory(child.id, 30); // 30 days
  
  return { family, parent1, parent2, child };
}

Test Isolation

// jest.setup.ts
beforeEach(async () => {
  await db.transaction.start();
});

afterEach(async () => {
  await db.transaction.rollback();
  jest.clearAllMocks();
});

CI/CD Test Pipeline

GitHub Actions Configuration

# .github/workflows/test.yml
name: Test Suite

on: [push, pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
      - run: npm ci
      - run: npm run test:unit
      - uses: codecov/codecov-action@v2

  integration-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
      redis:
        image: redis:7
    steps:
      - uses: actions/checkout@v2
      - run: npm ci
      - run: npm run test:integration

  e2e-tests:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci
      - run: npx detox build -c ios.sim.release
      - run: npx detox test -c ios.sim.release

Test Coverage Requirements

Minimum Coverage Thresholds

// jest.config.js
{
  "coverageThreshold": {
    "global": {
      "branches": 70,
      "functions": 80,
      "lines": 80,
      "statements": 80
    },
    "src/services/": {
      "branches": 85,
      "functions": 90
    },
    "src/components/": {
      "branches": 75,
      "functions": 85
    }
  }
}

Critical Path Coverage

  • Authentication flow: 100%
  • Activity logging: 95%
  • Real-time sync: 90%
  • AI responses: 85%
  • Offline queue: 90%

Test Reporting

Test Result Format

# Console output
PASS  src/components/FeedingTracker.test.tsx
  ✓ should start timer on selection (45ms)
  ✓ should validate minimum duration (23ms)
  ✓ should sync with family members (112ms)

Test Suites: 45 passed, 45 total
Tests:       234 passed, 234 total
Coverage:    82% statements, 78% branches
Time:        12.456s

Coverage Reports

  • HTML reports in /coverage/lcov-report/
  • Codecov integration for PR comments
  • SonarQube for code quality metrics