Testing Strategies

Comprehensive testing approaches for applications built with the Asterisms JS SDK Backend.

Overview

Testing is crucial for maintaining reliable applications. The Asterisms JS SDK Backend provides patterns and utilities to make testing easier and more effective across unit tests, integration tests, and end-to-end tests.

Test Environment Setup

Basic Test Configuration

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    globals: true,
    coverage: {
      reporter: ['text', 'html'],
      exclude: ['node_modules/', 'src/test/', '**/*.test.ts', '**/*.spec.ts']
    }
  }
});

Test Setup File

// src/test/setup.ts
import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import type { AsterismsBackendSDK } from '@asterisms/sdk-backend';

// Global test utilities
global.testSDK = null;
global.testUser = null;

beforeAll(async () => {
  // Global setup
  console.log('Setting up test environment...');
});

afterAll(async () => {
  // Global cleanup
  console.log('Cleaning up test environment...');
});

beforeEach(async () => {
  // Reset state before each test
  global.testSDK = null;
  global.testUser = null;
});

afterEach(async () => {
  // Cleanup after each test
  if (global.testSDK) {
    // Clean up any test data
  }
});

SDK Testing Utilities

Mock SDK Factory

// src/test/utils/mock-sdk.ts
import { vi } from 'vitest';
import type { AsterismsBackendSDK } from '@asterisms/sdk-backend';

export function createMockSDK(overrides: Partial<AsterismsBackendSDK> = {}): AsterismsBackendSDK {
  const mockStorage = {
    get: vi.fn(),
    set: vi.fn(),
    list: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    exists: vi.fn()
  };

  const mockAuth = {
    login: vi.fn(),
    logout: vi.fn(),
    validateToken: vi.fn(),
    hasPermission: vi.fn(),
    getCurrentUser: vi.fn(),
    refreshToken: vi.fn()
  };

  const mockRegistration = {
    getAppInfo: vi.fn(),
    updateCapabilities: vi.fn()
  };

  const mockNotifications = {
    send: vi.fn(),
    sendBulk: vi.fn(),
    getTemplates: vi.fn(),
    createTemplate: vi.fn()
  };

  const mockSDK = {
    storage: () => mockStorage,
    authorization: () => mockAuth,
    registration: () => mockRegistration,
    notifications: () => mockNotifications,
    boot: vi.fn(),
    isBooted: vi.fn().mockReturnValue(true),
    ...overrides
  };

  return mockSDK as AsterismsBackendSDK;
}

Test Data Factory

// src/test/utils/test-data.ts
import { faker } from '@faker-js/faker';

export const TestDataFactory = {
  user: (overrides = {}) => ({
    id: faker.string.uuid(),
    email: faker.internet.email(),
    name: faker.person.fullName(),
    role: faker.helpers.arrayElement(['admin', 'user', 'viewer']),
    createdAt: faker.date.past().toISOString(),
    updatedAt: faker.date.recent().toISOString(),
    ...overrides
  }),

  product: (overrides = {}) => ({
    id: faker.string.uuid(),
    name: faker.commerce.productName(),
    description: faker.commerce.productDescription(),
    price: parseFloat(faker.commerce.price()),
    category: faker.commerce.department(),
    createdAt: faker.date.past().toISOString(),
    updatedAt: faker.date.recent().toISOString(),
    ...overrides
  }),

  notification: (overrides = {}) => ({
    id: faker.string.uuid(),
    title: faker.lorem.sentence(),
    message: faker.lorem.paragraph(),
    type: faker.helpers.arrayElement(['info', 'warning', 'error', 'success']),
    recipient: faker.internet.email(),
    createdAt: faker.date.recent().toISOString(),
    ...overrides
  })
};

Unit Testing

Service Layer Tests

// src/lib/services/UserService.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UserService } from './UserService';
import { createMockSDK } from '../../test/utils/mock-sdk';
import { TestDataFactory } from '../../test/utils/test-data';

describe('UserService', () => {
  let userService: UserService;
  let mockSDK: ReturnType<typeof createMockSDK>;

  beforeEach(() => {
    mockSDK = createMockSDK();
    userService = new UserService(mockSDK);
  });

  describe('findAll', () => {
    it('should return all users', async () => {
      const mockUsers = [TestDataFactory.user(), TestDataFactory.user(), TestDataFactory.user()];

      mockSDK.storage().list.mockResolvedValue(mockUsers);

      const result = await userService.findAll();

      expect(mockSDK.storage().list).toHaveBeenCalledWith('users');
      expect(result).toEqual(mockUsers);
    });

    it('should handle storage errors', async () => {
      const error = new Error('Storage error');
      mockSDK.storage().list.mockRejectedValue(error);

      await expect(userService.findAll()).rejects.toThrow('Storage error');
    });
  });

  describe('findById', () => {
    it('should return user by id', async () => {
      const mockUser = TestDataFactory.user();
      mockSDK.storage().get.mockResolvedValue(mockUser);

      const result = await userService.findById(mockUser.id);

      expect(mockSDK.storage().get).toHaveBeenCalledWith('users', mockUser.id);
      expect(result).toEqual(mockUser);
    });

    it('should return null for non-existent user', async () => {
      mockSDK.storage().get.mockResolvedValue(null);

      const result = await userService.findById('non-existent-id');

      expect(result).toBeNull();
    });
  });

  describe('create', () => {
    it('should create new user', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        role: 'user' as const
      };

      const createdUser = TestDataFactory.user(userData);
      mockSDK.storage().create.mockResolvedValue(createdUser);

      const result = await userService.create(userData);

      expect(mockSDK.storage().create).toHaveBeenCalledWith(
        'users',
        expect.objectContaining({
          email: userData.email,
          name: userData.name,
          role: userData.role,
          id: expect.any(String),
          createdAt: expect.any(String),
          updatedAt: expect.any(String)
        })
      );
      expect(result).toEqual(createdUser);
    });
  });

  describe('findByEmail', () => {
    it('should find user by email', async () => {
      const mockUsers = [
        TestDataFactory.user({ email: 'user1@example.com' }),
        TestDataFactory.user({ email: 'user2@example.com' }),
        TestDataFactory.user({ email: 'user3@example.com' })
      ];

      mockSDK.storage().list.mockResolvedValue(mockUsers);

      const result = await userService.findByEmail('user2@example.com');

      expect(result).toEqual(mockUsers[1]);
    });

    it('should return null if user not found', async () => {
      mockSDK.storage().list.mockResolvedValue([]);

      const result = await userService.findByEmail('nonexistent@example.com');

      expect(result).toBeNull();
    });
  });
});

Controller Tests

// src/lib/controllers/UserController.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UserController } from './UserController';
import { UserService } from '../services/UserService';
import { createMockSDK } from '../../test/utils/mock-sdk';
import { TestDataFactory } from '../../test/utils/test-data';

// Mock UserService
vi.mock('../services/UserService');

describe('UserController', () => {
  let controller: UserController;
  let mockSDK: ReturnType<typeof createMockSDK>;
  let mockUserService: vi.Mocked<UserService>;
  let mockEvent: any;

  beforeEach(() => {
    mockSDK = createMockSDK();
    mockUserService = new UserService(mockSDK) as vi.Mocked<UserService>;
    controller = new UserController(mockSDK);

    // Replace the service instance
    (controller as any).userService = mockUserService;

    mockEvent = {
      locals: {
        sdk: mockSDK,
        user: TestDataFactory.user({ role: 'admin' })
      },
      request: {
        json: vi.fn()
      },
      params: {}
    };
  });

  describe('getUsers', () => {
    it('should return users for authenticated user', async () => {
      const mockUsers = [TestDataFactory.user(), TestDataFactory.user()];

      mockSDK.authorization().hasPermission.mockResolvedValue(true);
      mockUserService.findAll.mockResolvedValue(mockUsers);

      const response = await controller.getUsers(mockEvent);
      const responseData = await response.json();

      expect(response.status).toBe(200);
      expect(responseData.users).toEqual(mockUsers);
    });

    it('should return 401 for unauthenticated user', async () => {
      mockEvent.locals.user = null;

      const response = await controller.getUsers(mockEvent);
      const responseData = await response.json();

      expect(response.status).toBe(401);
      expect(responseData.error).toBe('Unauthorized');
    });

    it('should return 403 for insufficient permissions', async () => {
      mockSDK.authorization().hasPermission.mockResolvedValue(false);

      const response = await controller.getUsers(mockEvent);
      const responseData = await response.json();

      expect(response.status).toBe(403);
      expect(responseData.error).toContain('Missing permission');
    });
  });

  describe('createUser', () => {
    it('should create user successfully', async () => {
      const userData = {
        email: 'newuser@example.com',
        name: 'New User',
        role: 'user' as const
      };

      const createdUser = TestDataFactory.user(userData);

      mockEvent.request.json.mockResolvedValue(userData);
      mockSDK.authorization().hasPermission.mockResolvedValue(true);
      mockUserService.findByEmail.mockResolvedValue(null);
      mockUserService.create.mockResolvedValue(createdUser);

      const response = await controller.createUser(mockEvent);
      const responseData = await response.json();

      expect(response.status).toBe(201);
      expect(responseData.user).toEqual(createdUser);
    });

    it('should return 409 for duplicate email', async () => {
      const userData = {
        email: 'existing@example.com',
        name: 'Existing User',
        role: 'user' as const
      };

      const existingUser = TestDataFactory.user({ email: userData.email });

      mockEvent.request.json.mockResolvedValue(userData);
      mockSDK.authorization().hasPermission.mockResolvedValue(true);
      mockUserService.findByEmail.mockResolvedValue(existingUser);

      const response = await controller.createUser(mockEvent);
      const responseData = await response.json();

      expect(response.status).toBe(409);
      expect(responseData.error).toBe('User already exists');
    });
  });
});

Integration Testing

API Route Tests

// src/routes/api/users/+server.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { GET, POST, PUT, DELETE } from './+server';
import { createMockSDK } from '../../../test/utils/mock-sdk';
import { TestDataFactory } from '../../../test/utils/test-data';

describe('/api/users', () => {
  let mockSDK: ReturnType<typeof createMockSDK>;
  let mockEvent: any;

  beforeEach(() => {
    mockSDK = createMockSDK();
    mockEvent = {
      locals: {
        sdk: mockSDK,
        user: TestDataFactory.user({ role: 'admin' })
      },
      request: {
        json: vi.fn()
      },
      params: {}
    };
  });

  describe('GET /api/users', () => {
    it('should return users list', async () => {
      const mockUsers = [TestDataFactory.user(), TestDataFactory.user()];

      mockSDK.authorization().hasPermission.mockResolvedValue(true);
      mockSDK.storage().list.mockResolvedValue(mockUsers);

      const response = await GET(mockEvent);
      const data = await response.json();

      expect(response.status).toBe(200);
      expect(data.users).toEqual(mockUsers);
    });
  });

  describe('POST /api/users', () => {
    it('should create new user', async () => {
      const userData = {
        email: 'newuser@example.com',
        name: 'New User',
        role: 'user'
      };

      const createdUser = TestDataFactory.user(userData);

      mockEvent.request.json.mockResolvedValue(userData);
      mockSDK.authorization().hasPermission.mockResolvedValue(true);
      mockSDK.storage().list.mockResolvedValue([]); // No existing users
      mockSDK.storage().create.mockResolvedValue(createdUser);

      const response = await POST(mockEvent);
      const data = await response.json();

      expect(response.status).toBe(201);
      expect(data.user).toEqual(createdUser);
    });
  });
});

Database Integration Tests

// src/test/integration/storage.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';
import { createFetchAdapter } from '@asterisms/sdk-backend/adapters/fetch';
import { TestDataFactory } from '../utils/test-data';

describe('Storage Integration', () => {
  let sdk: AsterismsBackendSDK;
  let testData: any[] = [];

  beforeEach(async () => {
    sdk = getAsterismsBackendSDK({
      bundleId: 'com.test.integration',
      domain: 'test.asterisms.local',
      subdomain: 'test',
      httpServiceAdapterFactory: createFetchAdapter(fetch),
      registrationData: {
        bundleId: 'com.test.integration',
        capabilities: ['BACKEND_SERVICE'],
        description: 'Integration Test App',
        name: 'Test App',
        subdomain: 'test',
        title: 'Test App'
      }
    });

    await sdk.boot();
    testData = [];
  });

  afterEach(async () => {
    // Clean up test data
    const storage = sdk.storage();
    for (const item of testData) {
      try {
        await storage.delete('test_users', item.id);
      } catch (error) {
        // Ignore cleanup errors
      }
    }
  });

  it('should create and retrieve user', async () => {
    const storage = sdk.storage();
    const userData = TestDataFactory.user();

    const createdUser = await storage.create('test_users', userData);
    testData.push(createdUser);

    const retrievedUser = await storage.get('test_users', createdUser.id);

    expect(retrievedUser).toEqual(createdUser);
  });

  it('should list users', async () => {
    const storage = sdk.storage();
    const user1 = TestDataFactory.user();
    const user2 = TestDataFactory.user();

    const created1 = await storage.create('test_users', user1);
    const created2 = await storage.create('test_users', user2);
    testData.push(created1, created2);

    const users = await storage.list('test_users');

    expect(users).toContainEqual(created1);
    expect(users).toContainEqual(created2);
  });

  it('should update user', async () => {
    const storage = sdk.storage();
    const userData = TestDataFactory.user();

    const createdUser = await storage.create('test_users', userData);
    testData.push(createdUser);

    const updates = { name: 'Updated Name' };
    const updatedUser = await storage.update('test_users', createdUser.id, updates);

    expect(updatedUser.name).toBe('Updated Name');
    expect(updatedUser.id).toBe(createdUser.id);
  });

  it('should delete user', async () => {
    const storage = sdk.storage();
    const userData = TestDataFactory.user();

    const createdUser = await storage.create('test_users', userData);
    await storage.delete('test_users', createdUser.id);

    const retrievedUser = await storage.get('test_users', createdUser.id);
    expect(retrievedUser).toBeNull();
  });
});

End-to-End Testing

Playwright Setup

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:4173',
    trace: 'on-first-retry'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] }
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] }
    }
  ],
  webServer: {
    command: 'npm run build && npm run preview',
    port: 4173,
    reuseExistingServer: !process.env.CI
  }
});

E2E Test Examples

// e2e/user-management.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User Management', () => {
  test.beforeEach(async ({ page }) => {
    // Login as admin
    await page.goto('/login');
    await page.fill('[data-testid="email"]', 'admin@example.com');
    await page.fill('[data-testid="password"]', 'password');
    await page.click('[data-testid="login-button"]');

    // Wait for redirect to dashboard
    await page.waitForURL('/dashboard');
  });

  test('should create new user', async ({ page }) => {
    await page.goto('/users');

    // Click create user button
    await page.click('[data-testid="create-user-button"]');

    // Fill form
    await page.fill('[data-testid="user-email"]', 'newuser@example.com');
    await page.fill('[data-testid="user-name"]', 'New User');
    await page.selectOption('[data-testid="user-role"]', 'user');

    // Submit form
    await page.click('[data-testid="submit-button"]');

    // Verify success message
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();

    // Verify user appears in list
    await expect(page.locator('[data-testid="user-list"]')).toContainText('newuser@example.com');
  });

  test('should edit existing user', async ({ page }) => {
    await page.goto('/users');

    // Click edit button for first user
    await page.click('[data-testid="edit-user-button"]');

    // Update name
    await page.fill('[data-testid="user-name"]', 'Updated Name');

    // Submit form
    await page.click('[data-testid="submit-button"]');

    // Verify success message
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();

    // Verify updated name appears in list
    await expect(page.locator('[data-testid="user-list"]')).toContainText('Updated Name');
  });

  test('should delete user', async ({ page }) => {
    await page.goto('/users');

    // Click delete button for first user
    await page.click('[data-testid="delete-user-button"]');

    // Confirm deletion
    await page.click('[data-testid="confirm-delete"]');

    // Verify success message
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();

    // Verify user is removed from list
    await expect(page.locator('[data-testid="user-list"]')).not.toContainText('test@example.com');
  });
});

API Testing with Playwright

// e2e/api.spec.ts
import { test, expect } from '@playwright/test';

test.describe('API Endpoints', () => {
  let authToken: string;

  test.beforeAll(async ({ request }) => {
    // Login to get auth token
    const response = await request.post('/api/auth/login', {
      data: {
        email: 'admin@example.com',
        password: 'password'
      }
    });

    const data = await response.json();
    authToken = data.token;
  });

  test('GET /api/users should return users list', async ({ request }) => {
    const response = await request.get('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`
      }
    });

    expect(response.status()).toBe(200);

    const data = await response.json();
    expect(data.users).toBeDefined();
    expect(Array.isArray(data.users)).toBe(true);
  });

  test('POST /api/users should create new user', async ({ request }) => {
    const userData = {
      email: 'apitest@example.com',
      name: 'API Test User',
      role: 'user'
    };

    const response = await request.post('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`,
        'Content-Type': 'application/json'
      },
      data: userData
    });

    expect(response.status()).toBe(201);

    const data = await response.json();
    expect(data.user.email).toBe(userData.email);
    expect(data.user.name).toBe(userData.name);
  });

  test('PUT /api/users/:id should update user', async ({ request }) => {
    // First create a user
    const createResponse = await request.post('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`,
        'Content-Type': 'application/json'
      },
      data: {
        email: 'updatetest@example.com',
        name: 'Update Test User',
        role: 'user'
      }
    });

    const createdUser = await createResponse.json();

    // Then update the user
    const updateResponse = await request.put(`/api/users/${createdUser.user.id}`, {
      headers: {
        Authorization: `Bearer ${authToken}`,
        'Content-Type': 'application/json'
      },
      data: {
        name: 'Updated Name'
      }
    });

    expect(updateResponse.status()).toBe(200);

    const updatedData = await updateResponse.json();
    expect(updatedData.user.name).toBe('Updated Name');
  });
});

Performance Testing

Load Testing with Artillery

# artillery.yml
config:
  target: 'http://localhost:5173'
  phases:
    - duration: 60
      arrivalRate: 10
    - duration: 120
      arrivalRate: 20
    - duration: 60
      arrivalRate: 5
  payload:
    path: './test-data.csv'
    fields:
      - 'email'
      - 'name'

scenarios:
  - name: 'User CRUD Operations'
    weight: 70
    flow:
      - post:
          url: '/api/auth/login'
          json:
            email: 'admin@example.com'
            password: 'password'
          capture:
            - json: '$.token'
              as: 'authToken'
      - get:
          url: '/api/users'
          headers:
            Authorization: 'Bearer {{ authToken }}'
      - post:
          url: '/api/users'
          headers:
            Authorization: 'Bearer {{ authToken }}'
            Content-Type: 'application/json'
          json:
            email: '{{ email }}'
            name: '{{ name }}'
            role: 'user'

  - name: 'Dashboard Access'
    weight: 30
    flow:
      - get:
          url: '/dashboard'
      - get:
          url: '/api/stats'

Performance Test with Vitest

// src/test/performance/storage.perf.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';
import { TestDataFactory } from '../utils/test-data';

describe('Storage Performance', () => {
  let sdk: AsterismsBackendSDK;

  beforeEach(async () => {
    sdk = getAsterismsBackendSDK(testConfig);
    await sdk.boot();
  });

  it('should handle bulk operations efficiently', async () => {
    const storage = sdk.storage();
    const users = Array.from({ length: 1000 }, () => TestDataFactory.user());

    const startTime = performance.now();

    // Create users in batches
    const batchSize = 100;
    for (let i = 0; i < users.length; i += batchSize) {
      const batch = users.slice(i, i + batchSize);
      await Promise.all(batch.map((user) => storage.create('perf_test_users', user)));
    }

    const endTime = performance.now();
    const duration = endTime - startTime;

    // Expect to process 1000 users in under 5 seconds
    expect(duration).toBeLessThan(5000);

    console.log(`Processed ${users.length} users in ${duration.toFixed(2)}ms`);
  });

  it('should handle concurrent reads efficiently', async () => {
    const storage = sdk.storage();

    // Create test data
    const users = Array.from({ length: 100 }, () => TestDataFactory.user());
    const createdUsers = await Promise.all(
      users.map((user) => storage.create('perf_test_users', user))
    );

    const startTime = performance.now();

    // Perform concurrent reads
    const readPromises = createdUsers.map((user) => storage.get('perf_test_users', user.id));

    const results = await Promise.all(readPromises);

    const endTime = performance.now();
    const duration = endTime - startTime;

    // Expect all reads to complete
    expect(results).toHaveLength(100);
    expect(results.every((result) => result !== null)).toBe(true);

    // Expect to read 100 users in under 1 second
    expect(duration).toBeLessThan(1000);

    console.log(`Read ${results.length} users in ${duration.toFixed(2)}ms`);
  });
});

Test Automation

CI/CD Pipeline

# .github/workflows/test.yml
name: Test Suite

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm run test:unit

      - name: Run integration tests
        run: npm run test:integration

      - name: Build application
        run: npm run build

      - name: Run E2E tests
        run: npm run test:e2e

      - name: Upload coverage reports
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

Test Scripts

{
  "scripts": {
    "test": "vitest",
    "test:unit": "vitest run --reporter=verbose --coverage",
    "test:integration": "vitest run --config vitest.integration.config.ts",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:performance": "vitest run --config vitest.performance.config.ts",
    "test:watch": "vitest watch",
    "test:coverage": "vitest run --coverage"
  }
}

Best Practices

Test Organization

// ✅ Good: Organized test structure
describe('UserService', () => {
  describe('CRUD Operations', () => {
    describe('create', () => {
      it('should create user with valid data', async () => {
        // Test implementation
      });

      it('should reject invalid email', async () => {
        // Test implementation
      });
    });

    describe('findById', () => {
      it('should return user when found', async () => {
        // Test implementation
      });

      it('should return null when not found', async () => {
        // Test implementation
      });
    });
  });
});

// ❌ Bad: Flat test structure
describe('UserService', () => {
  it('should create user', async () => {});
  it('should find user', async () => {});
  it('should update user', async () => {});
  it('should delete user', async () => {});
});

Test Data Management

// ✅ Good: Use factories for consistent test data
const user = TestDataFactory.user({
  email: 'test@example.com',
  role: 'admin'
});

// ✅ Good: Clean up test data
afterEach(async () => {
  await cleanupTestData();
});

// ❌ Bad: Hardcoded test data
const user = {
  id: '123',
  email: 'test@example.com',
  name: 'Test User'
  // ... lots of hardcoded fields
};

Mock Management

// ✅ Good: Reset mocks between tests
beforeEach(() => {
  vi.clearAllMocks();
});

// ✅ Good: Verify mock calls
expect(mockSDK.storage().create).toHaveBeenCalledWith(
  'users',
  expect.objectContaining({
    email: 'test@example.com'
  })
);

// ❌ Bad: Shared mock state between tests
let mockSDK = createMockSDK();
// This mock will retain state between tests

Error Testing

// ✅ Good: Test error conditions
it('should handle network errors gracefully', async () => {
  mockSDK.storage().get.mockRejectedValue(new Error('Network error'));

  await expect(userService.findById('123')).rejects.toThrow('Network error');
});

// ✅ Good: Test specific error types
it('should handle validation errors', async () => {
  const invalidUser = { email: 'invalid-email' };

  await expect(userService.create(invalidUser)).rejects.toThrow('Invalid email format');
});

Debugging Tests

Test Debugging Setup

// src/test/debug.ts
import { beforeEach } from 'vitest';

beforeEach(() => {
  // Enable debug logging in tests
  if (process.env.NODE_ENV === 'test' && process.env.DEBUG) {
    console.log('=== Test Debug Mode ===');
  }
});

// Helper for debugging test state
export function debugTestState(label: string, data: any) {
  if (process.env.DEBUG) {
    console.log(`[DEBUG] ${label}:`, JSON.stringify(data, null, 2));
  }
}

Test Debugging Commands

# Run tests with debug output
DEBUG=true npm run test

# Run specific test file
npm run test -- UserService.test.ts

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run E2E tests in headed mode
npm run test:e2e -- --headed

# Run E2E tests in debug mode
npm run test:e2e -- --debug

Next Steps