# 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 ```javascript // Standard test file naming ComponentName.test.tsx ServiceName.test.ts utils.test.ts ``` ### Component Testing Example ```typescript // FeedingTracker.test.tsx describe('FeedingTracker', () => { it('should start timer on breast feeding selection', () => { const { getByTestId } = render(); fireEvent.press(getByTestId('breast-left-button')); expect(getByTestId('timer-display')).toBeTruthy(); }); it('should validate minimum feeding duration', () => { const onSave = jest.fn(); const { getByTestId } = render(); fireEvent.press(getByTestId('save-button')); expect(getByTestId('error-message')).toHaveTextContent('Feeding too short'); expect(onSave).not.toHaveBeenCalled(); }); }); ``` ### Service Testing Example ```typescript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```javascript // 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 ```javascript 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 ```javascript // 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 ```typescript // 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 ```typescript // 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(); const results = await axe(container); expect(results).toHaveNoViolations(); }); it('should support screen reader navigation', () => { const { getByLabelText } = render(); expect(getByLabelText('Log feeding')).toBeTruthy(); expect(getByLabelText('Select breast side')).toBeTruthy(); }); }); ``` ### One-Handed Operation Tests ```javascript // 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 ```typescript // 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 ```typescript // 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 ```typescript // jest.setup.ts beforeEach(async () => { await db.transaction.start(); }); afterEach(async () => { await db.transaction.rollback(); jest.clearAllMocks(); }); ``` --- ## CI/CD Test Pipeline ### GitHub Actions Configuration ```yaml # .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 ```json // 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 ```bash # 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