Savvi Studio

Integration Testing Examples

Runnable code examples demonstrating integration testing patterns

Overview

This directory contains complete, runnable examples that demonstrate various integration testing patterns and techniques. Each example is self-contained and thoroughly commented to serve as both documentation and learning material.

Examples

1. Basic Integration Test

File: 01-basic-integration.ts

The simplest possible integration test showing:

  • Importing test fixtures
  • Using the asSystem fixture
  • Basic query execution
  • Simple assertions

Complexity: ⭐ Beginner
Topics: Fixtures, Database queries, Assertions
Use when: Learning the basics of integration testing

2. Database + Codegen Integration

File: 02-database-codegen.ts

Demonstrates testing database schema and code generation:

  • Creating isolated test databases
  • Running migrations
  • Testing schema introspection
  • Validating generated types
  • Cleanup strategies

Complexity: ⭐⭐ Intermediate
Topics: Database isolation, Migrations, Codegen, Type safety
Use when: Testing database-driven features with type generation

3. Full Stack Integration

File: 03-full-stack.ts

Complete flow from API to database to response:

  • User authentication context
  • Graph operations
  • Database queries
  • Response validation
  • Multi-step workflows

Complexity: ⭐⭐⭐ Advanced
Topics: Authentication, Graph ops, End-to-end flows
Use when: Testing complete feature workflows

4. External Service Mocking

File: 04-external-service-mock.ts

How to test code that depends on external services:

  • Mocking WorkOS authentication
  • Mocking AWS services
  • Creating realistic mock responses
  • Testing error scenarios
  • Isolating external dependencies

Complexity: ⭐⭐ Intermediate
Topics: Mocking, External services, Error handling
Use when: Testing code with external dependencies

5. Custom Fixtures

File: 05-custom-fixtures.ts

Creating domain-specific test fixtures:

  • Extending base test with test.extend()
  • Creating reusable fixtures
  • Fixture dependencies
  • Lifecycle management
  • Type-safe fixtures

Complexity: ⭐⭐ Intermediate
Topics: Fixture creation, test.extend(), Type safety
Use when: Building reusable test infrastructure

6. Helper Utilities

File: 06-helper-utilities.ts

Shared helper functions and utilities:

  • Database helper classes
  • JWT generation utilities
  • Assertion helpers
  • Data factory functions
  • Test data builders

Complexity: ⭐⭐ Intermediate
Topics: Helpers, Utilities, Factories, Builders
Use when: Creating shared test utilities

Running Examples

Run All Examples

# Run all example tests
pnpm test docs/reference/testing/examples

Run Single Example

# Run a specific example
pnpm test docs/reference/testing/examples/01-basic-integration.ts

Watch Mode

# Watch examples for changes
pnpm test docs/reference/testing/examples -- --watch

Example Structure

Each example follows this structure:

/**
 * Example: [Name]
 * 
 * [Brief description]
 * 
 * Demonstrates:
 * - [Concept 1]
 * - [Concept 2]
 * - [Concept 3]
 * 
 * Related Documentation:
 * - [Link to guide 1]
 * - [Link to guide 2]
 */

import { describe, expect } from 'vitest';
import test from '../../integration/test';

describe('[Example Name]', () => {
    test('[test case]', async ({ fixtures }) => {
        // Well-commented implementation
    });
});

Learning Path

For Beginners:

  1. Start with 01-basic-integration.ts
  2. Read Getting Started Guide
  3. Try 02-database-codegen.ts
  4. Read Database Testing Guide

For Intermediate Developers:

  1. Review 03-full-stack.ts
  2. Study 05-custom-fixtures.ts
  3. Read Integration Patterns
  4. Create your own domain-specific fixtures

For Advanced Use Cases:

  1. Examine 04-external-service-mock.ts
  2. Review 06-helper-utilities.ts
  3. Read External Services Guide
  4. Build your own mock library

Tips for Using Examples

Copy and Modify

These examples are meant to be copied and adapted:

# Copy an example to your test directory
cp docs/testing/examples/01-basic-integration.ts \
   tests/integration/myfeature/mytest.test.ts

# Modify for your needs
# - Change test names
# - Adjust fixtures
# - Add your logic

Extract Patterns

Look for patterns you can reuse:

// Pattern: Database isolation
const dbName = `test_${Date.now()}_${randomString()}`;
await createDatabase(dbName);
try {
    // Your test
} finally {
    await dropDatabase(dbName);
}

// Pattern: User authentication
await asUser('user-123', 'org-456', async (client) => {
    // Test with user context
});

// Pattern: Assertion helpers
await assertions.userIdToBe('expected-user-id');

Combine Techniques

Mix and match patterns from different examples:

// Combine database + authentication + graph operations
test('complex workflow', async ({ 
    asUser, 
    graphAsRoot, 
    assertions 
}) => {
    // Setup with root
    await graphAsRoot(async (graph) => {
        // Create test data
    });
    
    // Test as user
    await asUser('user-123', 'org-456', async (client) => {
        // User operations
        await assertions.userIdToBe('user-123');
    });
});

Common Patterns

Database Isolation

// Create isolated test database
const dbName = `test_${Date.now()}_${randomString()}`;
await createTestDatabase(dbName);

try {
    // Run migrations
    await runMigrations(dbName);
    
    // Run tests
    await testDatabaseOperations(dbName);
} finally {
    // Always cleanup
    await dropTestDatabase(dbName);
}

Authentication Context

// Test with user context
await asUser('user-id', 'org-id', async (client) => {
    const result = await client.query(
        'SELECT current_user_id()'
    );
    expect(result.rows[0].current_user_id).toBe('user-id');
});

Custom Fixtures

// Extend base test
const test = baseTest.extend<MyFixtures>({
    myFixture: async ({ asSystem }, use) => {
        // Setup
        const resource = await createResource();
        
        // Provide to test
        await use(resource);
        
        // Cleanup
        await cleanupResource(resource);
    }
});

Error Testing

// Test error scenarios
await expect(async () => {
    await asUser('invalid', 'invalid', async (client) => {
        await client.query('INVALID SQL');
    });
}).rejects.toThrow();

Troubleshooting Examples

Example Won't Run

  1. Check dependencies: Ensure all imports are available
  2. Verify environment: Check .env.test configuration
  3. Database access: Verify database connection
  4. Migrations: Ensure migrations have been run
# Verify database
psql $DATABASE_URL -c "SELECT version();"

# Run migrations
pnpm run db:migrate

# Try running tests
pnpm test docs/testing/examples

Understanding Failures

When an example test fails:

  1. Read error message carefully
  2. Check test output for detailed information
  3. Run with verbose mode for more details:
    pnpm test docs/testing/examples/01-basic-integration.ts -- --reporter=verbose
    
  4. Add console.log statements to debug
  5. Consult Troubleshooting Guide

Modifying Examples

When adapting examples:

  1. Start small - Make one change at a time
  2. Test frequently - Run after each change
  3. Keep originals - Don't modify example files directly
  4. Document changes - Add comments explaining modifications

Contributing Examples

Have a useful pattern to share? Consider adding an example:

  1. Create a new example file following the naming convention
  2. Add thorough comments explaining each step
  3. Include error cases when relevant
  4. Update this README with description
  5. Test thoroughly before submitting
  6. Link to relevant documentation

Questions?

  • Can't find what you need? Check the Integration Patterns guide
  • Example not working? See Troubleshooting Guide
  • Want to contribute? Follow the contributing guidelines above
  • Need help? Use /reportbug to report issues

Start learning: 01-basic-integration.ts
Need help? Getting Started Guide