Skip to content

Latest commit

 

History

History
302 lines (241 loc) · 7.36 KB

File metadata and controls

302 lines (241 loc) · 7.36 KB

Example: Updating admin.test.ts to Use Mock Factory

File: /tests/unit/admin.test.ts

This example shows the exact changes needed to fix the missing rpc() method issue.


Current Code (Lines 1-40)

/**
 * Admin Service Unit Tests
 */

import { describe, test, expect, vi, beforeEach } from 'vitest';
import * as adminService from '@/lib/supabase/services/admin';
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@/lib/supabase/types-compat';

describe('Admin Service', () => {
  let mockSupabase: any;

  beforeEach(() => {
    mockSupabase = {
      from: vi.fn().mockReturnThis(),
      select: vi.fn().mockReturnThis(),
      insert: vi.fn().mockReturnThis(),
      update: vi.fn().mockReturnThis(),
      eq: vi.fn().mockReturnThis(),
      ilike: vi.fn().mockReturnThis(),
      or: vi.fn().mockReturnThis(),
      gte: vi.fn().mockReturnThis(),
      order: vi.fn().mockReturnThis(),
      limit: vi.fn().mockReturnThis(),
      range: vi.fn().mockReturnThis(),
      single: vi.fn().mockResolvedValue({ data: null, error: null }),
      rpc: vi.fn().mockReturnThis(), // ❌ PROBLEM: No maybeSingle() support!
    };
  });

  // ... tests follow
});

Problem

The manual rpc: vi.fn().mockReturnThis() doesn't properly chain to .maybeSingle() or .single(), causing tests to fail when the service calls rpc('function_name').maybeSingle().


Updated Code (Lines 1-40)

/**
 * Admin Service Unit Tests
 */

import { describe, test, expect, vi, beforeEach } from 'vitest';
import * as adminService from '@/lib/supabase/services/admin';
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@/lib/supabase/types-compat';
// ✅ ADD: Import mock factory
import { createSupabaseMock, createRpcMock } from '@/tests/helpers/supabase-mock';

describe('Admin Service', () => {
  let mockSupabase: any;

  beforeEach(() => {
    // ✅ CHANGE: Use factory instead of manual mock
    mockSupabase = createSupabaseMock();
  });

  // ... tests follow (mostly unchanged)
});

Changes

  1. Import added (line 10): Import the mock factory helpers
  2. Mock creation simplified (line 17): Replace 30 lines of manual setup with 1 line

Specific Test Updates

Test: getPlatformStats (Lines 917-1018)

Before (Lines 936-1001)

test('calculates platform statistics correctly', async () => {
  let fromCallCount = 0;

  mockSupabase.from.mockImplementation((table) => {
    fromCallCount++;
    if (fromCallCount === 1) {
      // verifyAdminAccess
      return {
        select: vi.fn().mockReturnThis(),
        eq: vi.fn().mockReturnThis(),
        single: vi.fn().mockResolvedValue({
          data: { user_type: 'admin' },
          error: null,
        }),
      };
    }

    // Stats queries
    if (table === 'users' && fromCallCount === 2) {
      return {
        select: vi.fn().mockResolvedValue({ count: 250, error: null }),
      };
    }
    // ... more table mocks
  });

  const result = await adminService.getPlatformStats(
    mockSupabase as SupabaseClient<Database>,
    'admin-user-123'
  );

  expect(result).toEqual({
    totalUsers: 250,
    // ...
  });
});

After (Lines 936-970)

test('calculates platform statistics correctly', async () => {
  let fromCallCount = 0;

  mockSupabase.from = vi.fn((table) => {
    fromCallCount++;
    if (fromCallCount === 1) {
      // verifyAdminAccess
      return {
        select: vi.fn().mockReturnThis(),
        eq: vi.fn().mockReturnThis(),
        single: vi.fn().mockResolvedValue({
          data: { user_type: 'admin' },
          error: null,
        }),
      };
    }

    // Stats queries - same as before
    if (table === 'users' && fromCallCount === 2) {
      return {
        select: vi.fn().mockResolvedValue({ count: 250, error: null }),
      };
    }
    // ... more table mocks
  });

  // ✅ ADD: Mock RPC function for optimized stats
  mockSupabase.rpc = createRpcMock('get_platform_stats_aggregated', {
    data: {
      total_users: 250,
      total_students: 150,
      total_preceptors: 80,
      total_matches: 200,
      active_matches: 50,
      total_payments: 120,
      recent_signups: 15,
      pending_matches: 30,
    },
    error: null,
  });

  const result = await adminService.getPlatformStats(
    mockSupabase as SupabaseClient<Database>,
    'admin-user-123'
  );

  expect(result).toEqual({
    totalUsers: 250,
    totalStudents: 150,
    totalPreceptors: 80,
    totalMatches: 200,
    activeMatches: 50,
    totalPayments: 120,
    recentSignups: 15,
    pendingMatches: 30,
  });
});

Changes

  1. RPC mock added (line 32): Use createRpcMock() to properly mock the RPC function
  2. Supports maybeSingle(): Now works when service calls rpc('get_platform_stats_aggregated').maybeSingle()

Alternative: Using Global Setup

For even cleaner tests, you can use global setup:

import { describe, test, expect, beforeEach } from 'vitest';
import * as adminService from '@/lib/supabase/services/admin';
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@/lib/supabase/types-compat';
import {
  setupSupabaseMock,
  getSupabaseMock,
  configureRpcMock,
  MockPresets
} from '@/tests/helpers/vi-setup';

describe('Admin Service', () => {
  setupSupabaseMock(); // Auto-resets before each test

  test('calculates platform statistics correctly', async () => {
    const mockSupabase = getSupabaseMock();

    // Configure admin user lookup
    let fromCallCount = 0;
    mockSupabase.from = vi.fn((table) => {
      fromCallCount++;
      if (fromCallCount === 1 && table === 'users') {
        return createSupabaseMock(MockPresets.userLookup('admin-123', 'admin'));
      }
      // ... other tables
      return createSupabaseMock();
    });

    // Configure RPC
    configureRpcMock('get_platform_stats_aggregated', {
      data: {
        total_users: 250,
        total_students: 150,
        total_preceptors: 80,
      },
      error: null,
    });

    const result = await adminService.getPlatformStats(
      mockSupabase as SupabaseClient<Database>,
      'admin-user-123'
    );

    expect(result.totalUsers).toBe(250);
  });
});

Full Diff Summary

Files Changed: 1

  • /tests/unit/admin.test.ts

Lines Changed: ~5

  1. Line 10: Add import statement
  2. Line 17: Replace manual mock with createSupabaseMock()
  3. Lines 32-42 (in each RPC test): Add createRpcMock() calls

Tests Fixed: ~10

All tests in admin.test.ts that use getPlatformStats which calls RPC functions.

Time Required: ~5 minutes


Verification

Run the test to verify it works:

npx vitest run tests/unit/admin.test.ts

Expected output:

✓ tests/unit/admin.test.ts (38 tests) 25ms
  ✓ Admin Service (38)
    ✓ getAuditLogsForEntity (6)
    ✓ getRecentPaymentEvents (5)
    ✓ listUsers (6)
    ✓ searchUsers (5)
    ✓ updateUserType (5)
    ✓ getPlatformStats (6) ← NOW WORKS!
    ✓ getRecentAuditLogs (6)
    ✓ Edge Cases (4)

Pattern Recognition

This same pattern applies to all other test files:

  1. Import mock factory helpers
  2. Replace manual mock setup with createSupabaseMock()
  3. Add createRpcMock() for any RPC function calls
  4. Optionally use MockPresets for common scenarios
  5. Optionally use global setup for cleaner code

That's it! 🎉