External LibrariesDeveloper Workflow

Developer Workflow

A complete guide for developers who prefer code-first testing.

Philosophy

┌─────────────────────────────────────────────────────────────┐
│                    Separation of Concerns                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Developers                        QA Team                 │
│   ──────────                        ───────                 │
│   • Write test logic                • Build test flows      │
│   • Create assertions               • Configure environments│
│   • Build generators                • Select tests by tags  │
│   • Define test cases               • Run and monitor       │
│   • Version control                 • Report results        │
│   • Code review                     • No coding required    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Project Structure

Recommended layout for your external library:

my-test-lib/
├── src/
│   ├── assertions/           # Custom assertion functions
│   │   ├── auth.ts
│   │   ├── payments.ts
│   │   ├── users.ts
│   │   └── index.ts
│   │
│   ├── generators/           # Value generators
│   │   ├── users.ts
│   │   ├── orders.ts
│   │   └── index.ts
│   │
│   ├── hooks/                # API hooks
│   │   ├── auth-signature.ts
│   │   ├── logging.ts
│   │   └── index.ts
│   │
│   ├── test-cases/           # External test cases
│   │   ├── auth/
│   │   │   ├── login.ts
│   │   │   ├── logout.ts
│   │   │   └── index.ts
│   │   ├── users/
│   │   │   ├── crud.ts
│   │   │   └── index.ts
│   │   ├── payments/
│   │   │   ├── checkout.ts
│   │   │   ├── refund.ts
│   │   │   └── index.ts
│   │   └── index.ts
│   │
│   ├── utils/                # Shared utilities
│   │   ├── crypto.ts
│   │   ├── formatters.ts
│   │   └── index.ts
│   │
│   ├── schemas/              # Zod schemas
│   │   ├── user.ts
│   │   ├── order.ts
│   │   └── index.ts
│   │
│   ├── types.ts              # TypeScript types
│   └── index.ts              # Main export

├── tests/                    # Unit tests for your test cases
│   ├── assertions/
│   ├── generators/
│   └── test-cases/

├── dist/                     # Build output
├── package.json
├── tsconfig.json
├── rollup.config.js
└── README.md

Local Development

Setup

# Clone template
git clone https://github.com/ReAPI-com/external-lib-template my-test-lib
cd my-test-lib
npm install

Development Loop

# Watch mode - rebuild on changes
npm run dev
 
# Run unit tests
npm test
 
# Lint code
npm run lint
 
# Build for production
npm run build

Type Checking

The template includes types for ReAPI globals:

// src/types/globals.d.ts
declare const $context: Record<string, any>;
declare const $vars: Record<string, any>;
declare const $secrets: Record<string, any>;
declare const $server: { baseUrl: string; name: string; id: string };
declare const $auth: { type: string; headers: Record<string, string> };
declare function $addAssertionResult(result: AssertionResult): void;
declare function $log(level: string, message: string, data?: any): void;
 
// Built-in libraries
declare const _: typeof import("lodash");
declare const faker: typeof import("@faker-js/faker").faker;
declare const z: typeof import("zod").z;
declare const ky: typeof import("ky").default;
declare const luxon: typeof import("luxon");

Unit Testing Your Test Cases

Test your test logic before deployment:

// tests/test-cases/auth.test.ts
import { describe, it, expect, vi } from "vitest";
import { loginTest } from "../../src/test-cases/auth";
 
// Mock ReAPI globals
const mockAddAssertionResult = vi.fn();
global.$addAssertionResult = mockAddAssertionResult;
global.$server = { baseUrl: "http://localhost:3000" };
global.$vars = { testUserEmail: "test@example.com" };
global.$secrets = { testUserPassword: "password123" };
global.$auth = { headers: {} };
global.$context = {}; // Shared context mock
 
describe("loginTest", () => {
  beforeEach(() => {
    // Reset context before each test
    global.$context = {};
  });
 
  it("should pass with valid credentials", async () => {
    // Mock fetch
    global.fetch = vi.fn().mockResolvedValue({
      status: 200,
      json: () => Promise.resolve({ token: "abc123" }),
    });
 
    await loginTest.function();
 
    expect(mockAddAssertionResult).toHaveBeenCalledWith(
      expect.objectContaining({ passed: true })
    );
  });
 
  it("should fail with invalid credentials", async () => {
    global.fetch = vi.fn().mockResolvedValue({
      status: 401,
      json: () => Promise.resolve({ error: "Invalid credentials" }),
    });
 
    await loginTest.function();
 
    expect(mockAddAssertionResult).toHaveBeenCalledWith(
      expect.objectContaining({ passed: false })
    );
  });
});

CI/CD Integration

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy Test Library
 
on:
  push:
    branches: [main]
  release:
    types: [published]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npm test
      - run: npm run lint
 
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'release'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          registry-url: "https://registry.npmjs.org"
      - run: npm ci
      - run: npm run build
      
      - name: Deploy to ReAPI
        env:
          REAPI_API_KEY: ${{ secrets.REAPI_API_KEY }}
        run: npx @reapi/cli lib sync --project ${{ vars.REAPI_PROJECT_ID }}

Version Tagging

# Bump version
npm version patch  # 1.0.0 -> 1.0.1
npm version minor  # 1.0.0 -> 1.1.0
npm version major  # 1.0.0 -> 2.0.0
 
# Push with tags
git push && git push --tags

Collaboration with QA

Documentation in Code

export const paymentRefundTest: TestCaseDefinition = {
  id: "payment-refund-flow",
  name: "Payment Refund Flow",
  description: `
    Tests the complete refund process:
    1. Initiates a refund for an existing order
    2. Verifies status transitions (pending → processing → completed)
    3. Confirms final balance adjustment
    
    Prerequisites:
    - Requires $context.orderId from a previous order creation
    - Order must be in "paid" status
    
    Tags:
    - payments: Payment-related tests
    - refunds: Refund-specific tests
    - regression: Full regression suite
  `,
  tags: ["payments", "refunds", "regression"],
  priority: 2,
  timeout: 60000,
  function: async () => {
    // Implementation
  },
};

Tagging Convention

Document your tagging strategy for QA:

// src/docs/TAGGING.md or in README
 
/**
 * Tagging Convention
 * ==================
 * 
 * Feature Tags:
 *   - auth: Authentication and authorization
 *   - users: User management
 *   - payments: Payment processing
 *   - orders: Order management
 * 
 * Test Type Tags:
 *   - smoke: Quick sanity checks (< 5 min total)
 *   - regression: Full test suite
 *   - e2e: End-to-end flows
 * 
 * Priority Tags:
 *   - critical: Must pass for deployment
 *   - p1: High priority
 *   - p2: Medium priority
 * 
 * Environment Tags:
 *   - prod-safe: Safe to run in production
 *   - staging-only: Only run in staging
 */

Changelog

Keep QA informed of changes:

# Changelog
 
## [1.2.0] - 2024-01-15
 
### Added
- `payment-refund-flow` test case
- `isValidIBAN` assertion
 
### Changed
- `auth-login` now stores userId in context
 
### Deprecated
- `old-payment-validator` - use `isValidPayment` instead

Environment-Specific Logic

Handle different environments in test cases:

function: async () => {
  const isProd = $server.name.toLowerCase().includes("prod");
  const isStaging = $server.name.toLowerCase().includes("staging");
 
  // Skip destructive tests in production
  if (isProd && testConfig.destructive) {
    $log("warn", "Skipping destructive test in production");
    $addAssertionResult({
      passed: true,
      message: "Skipped in production",
      operator: "skip",
      leftValue: "production",
      rightValue: "non-production",
    });
    return;
  }
 
  // Use different test data per environment
  const testUser = isProd
    ? $vars.prodTestUser
    : $vars.testUser;
 
  // Continue with test...
}

Debugging

Local Debugging

// Add verbose logging during development
function: async () => {
  console.log("Debug: Starting test");
  console.log("Debug: Server:", $server);
  console.log("Debug: Vars:", $vars);
  
  // Your test logic
  
  console.log("Debug: Response:", response);
}

Production Logging

// Use $log for production-safe logging
function: async () => {
  $log("info", "Test started", { server: $server.name });
  
  // Your test logic
  
  $log("debug", "Response received", { 
    status: response.status,
    // Don't log sensitive data
    hasToken: !!data.token,
  });
}