modh-labs

testing-patterns

0
0
# Install this skill:
npx skills add modh-labs/ai-software-os --skill "testing-patterns"

Install specific skill from multi-skill repository

# Description

Create tests following your project's Vitest and Playwright patterns. Use when writing unit tests, integration tests, mocking Supabase, testing server actions, or running test suites. Enforces __tests__ directories, repository mocking, and test data cleanup.

# SKILL.md


name: testing-patterns
description: Create tests following your project's Vitest and Playwright patterns. Use when writing unit tests, integration tests, mocking Supabase, testing server actions, or running test suites. Enforces tests directories, repository mocking, and test data cleanup.
allowed-tools: Read, Grep, Glob


Testing Patterns Skill

When This Skill Activates

This skill automatically activates when you:
- Write or modify test files (*.test.ts, *.test.tsx)
- Create mocks for Supabase, repositories, or services
- Discuss testing strategy or coverage
- Run test commands (bun test, bun test:ci)

Commands

bun test          # Watch mode (re-run on changes)
bun test:ci       # Single run (CI mode)
bun test:ui       # Interactive UI
bun test:e2e      # Playwright headed mode
bun ci            # Full pipeline (lint + typecheck + tests)

Test Organization

Tests use __tests__/ directories next to source code:

app/(protected)/calls/actions/
β”œβ”€β”€ cancel-reschedule-call.ts
└── __tests__/
    β”œβ”€β”€ cancel-flow.test.ts
    └── reschedule-flow.test.ts

Naming Convention

  • Unit tests: *.test.ts
  • Integration tests: *-integration.test.ts
  • E2E tests: *.e2e.test.ts

4 Testing Patterns

Pattern 1: Unit Test Repository (Mock Supabase)

Test repository logic in isolation with mocked Supabase client:

import { describe, it, expect, beforeEach } from "vitest";
import { createMockSupabaseClient } from "@/test/mocks/supabase.mock";
import { createSale } from "../sales.repository";

describe("sales.repository", () => {
  let mockSupabase: ReturnType<typeof createMockSupabaseClient>;

  beforeEach(() => {
    mockSupabase = createMockSupabaseClient();
  });

  it("should create a sale", async () => {
    mockSupabase._chain.insert.mockReturnValue(mockSupabase._chain);
    mockSupabase._chain.select.mockReturnValue(mockSupabase._chain);
    mockSupabase._chain.single.mockResolvedValue({
      data: { id: "sale_123", amount: 1000 },
      error: null,
    });

    const result = await createSale(mockSupabase as any, {
      call_id: "call_456",
      amount: 1000,
    });

    expect(result.amount).toBe(1000);
    expect(mockSupabase.from).toHaveBeenCalledWith("sales");
  });
});

Pattern 2: Integration Test Repository (Real Supabase)

Test against real database for RLS, constraints, indexes:

describe("sales.repository (integration)", () => {
  let supabase: Awaited<ReturnType<typeof createServiceRoleClient>>;
  let testOrgId: string;

  beforeEach(async () => {
    supabase = await createServiceRoleClient();
    testOrgId = `test_org_${Date.now()}`;
  });

  afterEach(async () => {
    await supabase.from("sales").delete().eq("organization_id", testOrgId);
  });

  it("should create and retrieve sales", async () => {
    const sale = await createSale(supabase, { organization_id: testOrgId, amount: 1000 });
    expect(sale.id).toBeDefined();
  });
});

Pattern 3: Unit Test Server Action (Mock Repositories)

Mock at the repository boundary β€” never mock internal functions:

import { vi } from "vitest";
import * as salesRepository from "@/app/_shared/repositories/sales.repository";

vi.mock("@/app/_shared/repositories/sales.repository", () => ({
  updateSalesStatusByCallId: vi.fn(),
}));

describe("updatePaymentStatusAction", () => {
  beforeEach(() => { vi.clearAllMocks(); });

  it("should update payment status", async () => {
    vi.spyOn(salesRepository, "updateSalesStatusByCallId").mockResolvedValue(undefined);

    const result = await updatePaymentStatusAction({ callId: "call_123", status: "paid" });
    expect(result.success).toBe(true);
  });
});

Pattern 4: Integration Test Server Action (Real Repositories)

Full flow with actual database:

describe("updatePaymentStatusAction (integration)", () => {
  // Setup real test data in beforeEach
  // Clean up in afterEach
  // Test the full action β†’ repository β†’ database flow
});

Core Rules

DO

  • βœ… Use __tests__/ directory next to source code
  • βœ… Mock at repository boundary for action tests
  • βœ… Use createMockSupabaseClient from @/test/mocks/supabase.mock
  • βœ… Clean up test data in afterEach
  • βœ… Use unique test IDs (test_org_${Date.now()})
  • βœ… beforeEach cleanup of all mocks (vi.clearAllMocks())
  • βœ… Mock env vars before dynamic imports

DON'T

  • ❌ Use supabase.from() directly in tests (use repositories)
  • ❌ Mock internal functions (mock at boundary)
  • ❌ Mix unit and integration tests in same describe block
  • ❌ Skip cleanup (causes flaky tests)
  • ❌ Hardcode test data (use unique IDs or faker)

Quick Reference

Pattern Mock What Test What
Unit repo Supabase client Query building, error handling
Integration repo Nothing (real DB) RLS, constraints, data flow
Unit action Repository functions Business logic, validation
Integration action Nothing (real DB) Full flow end-to-end

Detailed Documentation

  • Mock Supabase client details: references/mock-supabase-pattern.md
  • Testing docs: docs/patterns/testing.md
  • Repository testing: docs/patterns/repository-testing.md

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.