name: testing
description: Testing strategy, test patterns, and commands for unit, integration, and E2E tests. Use when writing tests or understanding testing approach.
user-invocable: true
Testing Strategy
Testing Layers
Unit Tests
- Test individual functions and utilities
- Mock external dependencies
- Fast execution
Integration Tests
- Test API routes end-to-end
- Test database operations
- Use test database
Multi-Tenant Testing
- CRITICAL: Always test tenant isolation
- Create test data for multiple tenants
- Verify cross-tenant access is blocked
Test Structure
// __tests__/services/order-service.test.ts
import { OrderService } from '@/lib/services/order-service';
import { prisma } from '@/lib/db/prisma';
describe('OrderService', () => {
let service: OrderService;
const tenantId1 = 'tenant-123';
const tenantId2 = 'tenant-456';
beforeEach(async () => {
service = new OrderService();
// Setup test data
});
afterEach(async () => {
// Cleanup test data
});
it('should create order for tenant', async () => {
const order = await service.createOrder({
customerId: 'cust-123',
items: [{ productId: 'prod-1', quantity: 2 }]
});
expect(order.tenant_org_id).toBe(tenantId1);
});
it('should not access other tenant data', async () => {
// Create order for tenant1
const order1 = await service.createOrder({ ... }, tenantId1);
// Try to access as tenant2
await expect(
service.getOrder(order1.id, tenantId2)
).rejects.toThrow('Not found');
});
});
Multi-Tenant Test Pattern
describe('Tenant Isolation', () => {
const tenant1 = 'tenant-aaa';
const tenant2 = 'tenant-bbb';
beforeEach(async () => {
// Create test data for both tenants
await createTestData(tenant1, { orders: 5 });
await createTestData(tenant2, { orders: 3 });
});
it('tenant1 should only see their data', async () => {
const orders = await getOrders(tenant1);
expect(orders.length).toBe(5);
orders.forEach(order => {
expect(order.tenant_org_id).toBe(tenant1);
});
});
it('tenant2 should only see their data', async () => {
const orders = await getOrders(tenant2);
expect(orders.length).toBe(3);
orders.forEach(order => {
expect(order.tenant_org_id).toBe(tenant2);
});
});
it('should prevent cross-tenant access', async () => {
const tenant1Order = await createOrder(tenant1);
// Attempt to access tenant1's order as tenant2
await expect(
getOrder(tenant1Order.id, tenant2)
).rejects.toThrow();
});
});
API Route Testing
import { NextRequest } from 'next/server';
import { GET, POST } from '@/app/api/v1/orders/route';
describe('GET /api/v1/orders', () => {
it('should return orders for tenant', async () => {
const request = new NextRequest('http://localhost/api/v1/orders');
// Mock auth session
mockSession({ tenantId: 'tenant-123' });
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.data).toBeInstanceOf(Array);
});
it('should require authentication', async () => {
const request = new NextRequest('http://localhost/api/v1/orders');
const response = await GET(request);
expect(response.status).toBe(401);
});
});
Test Commands
# Run all tests
npm test
# Run specific test file
npm test -- order-service.test.ts
# Run tests in watch mode
npm test -- --watch
# Run tests with coverage
npm test -- --coverage
# Run only unit tests
npm test -- --testPathPattern=unit
# Run only integration tests
npm test -- --testPathPattern=integration
Test Database Setup
// tests/setup.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.TEST_DATABASE_URL
}
}
});
beforeAll(async () => {
// Run migrations
await prisma.$executeRaw`CREATE SCHEMA IF NOT EXISTS test`;
});
afterAll(async () => {
// Cleanup
await prisma.$disconnect();
});
Mocking
// Mock Supabase client
jest.mock('@/lib/supabase/client', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn().mockResolvedValue({ data: [], error: null }),
insert: jest.fn().mockResolvedValue({ data: {}, error: null }),
})),
},
}));
// Mock auth session
jest.mock('@/lib/db/tenant-context', () => ({
getTenantIdFromSession: jest.fn().mockResolvedValue('tenant-123'),
}));
Testing Checklist
Additional Resources
- See reference.md for complete testing documentation