reboottime / frontend-testing

A repository gathered some best practices of doing testing in frontend development. Topics covered in this repository include unit testing, integration testing and e2e testing.
1 stars 0 forks source link

Explain E2E testing using real database and api #13

Open reboottime opened 1 week ago

reboottime commented 1 week ago

code logic example


// test-setup/database.ts
import { Pool } from 'pg';
import { Knex, knex } from 'knex';

export class TestDatabase {
  private db: Knex;

  constructor() {
    // Connect to test database
    this.db = knex({
      client: 'postgresql',
      connection: process.env.TEST_DATABASE_URL,
      pool: { min: 2, max: 10 }
    });
  }

  async setup() {
    try {
      // Run migrations to ensure schema is up to date
      await this.db.migrate.latest({
        directory: './migrations'
      });

      // Run any base seeds needed (e.g., system configs, constants)
      await this.db.seed.run({
        directory: './seeds/base'
      });

    } catch (error) {
      console.error('Database setup failed:', error);
      throw error;
    }
  }

  async cleanup() {
    // Clean up all test data - order matters due to foreign keys
    await this.db.raw('TRUNCATE TABLE organizations CASCADE');
    await this.db.raw('TRUNCATE TABLE users CASCADE');
    await this.db.raw('TRUNCATE TABLE wallets CASCADE');
    // ... other tables
  }

  async createTestData(testCase: string) {
    // Create isolated data for specific test case
    const testOrg = await this.db('organizations').insert({
      name: `Test Org ${testCase}`,
      created_at: new Date(),
      // ... other fields
    }).returning('*');

    return testOrg[0];
  }
}

// test-setup/api-client.ts
export class TestApiClient {
  private baseUrl: string;
  private authToken?: string;

  constructor() {
    this.baseUrl = process.env.API_URL!;
  }

  async authenticate() {
    // Get auth token for API requests
    const response = await fetch(`${this.baseUrl}/auth/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: process.env.TEST_ADMIN_EMAIL,
        password: process.env.TEST_ADMIN_PASSWORD
      })
    });

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

  async createOrganization(data: any) {
    const response = await fetch(`${this.baseUrl}/api/organizations`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.authToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    return response.json();
  }

  // Add other API methods as needed
}

// test-setup/test-context.ts
import { test as base } from '@playwright/test';
import { TestDatabase } from './database';
import { TestApiClient } from './api-client';

// Define your test fixtures
type TestFixtures = {
  db: TestDatabase;
  api: TestApiClient;
  testOrg: any;
};

// Create the test with fixtures
export const test = base.extend<TestFixtures>({
  // Database fixture - shared across all tests in a worker
  db: [async ({}, use) => {
    const db = new TestDatabase();
    await db.setup();
    await use(db);
    await db.cleanup();
  }, { scope: 'worker' }],

  // API client fixture - new for each test
  api: async ({}, use) => {
    const api = new TestApiClient();
    await api.authenticate();
    await use(api);
  },

  // Test organization fixture - new for each test
  testOrg: async ({ db, api }, use) => {
    // Create test organization using API
    const org = await api.createOrganization({
      name: `Test Org ${Date.now()}`,
      type: 'business'
    });

    await use(org);

    // Cleanup after test
    await db.cleanup();
  }
});

// Example test using the setup
test('complete organization onboarding', async ({ page, testOrg, api }) => {
  // Start from onboarding page with real org ID
  await page.goto(`${process.env.PORTAL_URL}/onboarding/${testOrg.id}`);

  // Fill onboarding form
  await page.fill('[data-testid="business-name"]', 'Updated Name Inc');
  await page.fill('[data-testid="tax-id"]', '123456789');
  await page.click('[data-testid="submit-button"]');

  // Verify in real database via API
  const updatedOrg = await api.getOrganization(testOrg.id);
  expect(updatedOrg.name).toBe('Updated Name Inc');
  expect(updatedOrg.status).toBe('active');
});
reboottime commented 1 week ago

API Testing Flow:

image