API Testing Guide

Learn how to thoroughly test your Stateset API integration with comprehensive testing strategies, tools, and real-world examples. This guide covers everything from unit tests to end-to-end testing scenarios.

Prerequisites

Before you begin, ensure you have:

  • A Stateset test environment account
  • Testing API credentials (separate from production)
  • Node.js 16+ installed
  • Basic understanding of testing frameworks
  • Access to testing tools (Jest, Mocha, etc.)

Testing Environment Setup

1

Configure Test Environment

Set up separate test credentials and environment:

# .env.test
STATESET_API_KEY=sk_test_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=test
STATESET_BASE_URL=https://api-test.stateset.com

# Test Database
TEST_DATABASE_URL=postgresql://test_user:test_password@localhost:5432/stateset_test

# Mock Settings
ENABLE_API_MOCKING=true
MOCK_EXTERNAL_SERVICES=true
2

Install Testing Dependencies

Install necessary testing tools and libraries:

npm install --save-dev \
  jest \
  supertest \
  nock \
  @testing-library/jest-dom \
  msw \
  faker \
  test-containers

# For TypeScript projects
npm install --save-dev \
  @types/jest \
  @types/supertest \
  ts-jest
3

Configure Jest

Create comprehensive Jest configuration:

// jest.config.js
module.exports = {
  testEnvironment: 'node',
  setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
  testMatch: [
    '**/tests/**/*.test.js',
    '**/tests/**/*.spec.js'
  ],
  collectCoverageFrom: [
    'src/**/*.js',
    '!src/**/*.test.js',
    '!src/tests/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  testTimeout: 30000,
  maxWorkers: 4
};

Unit Testing

Testing API Client Methods

Test your Stateset API client methods in isolation:

// tests/unit/stateset-client.test.js
import { StateSetClient } from 'stateset-node';
import nock from 'nock';

describe('StateSetClient', () => {
  let client;
  const apiKey = process.env.STATESET_API_KEY;
  const baseURL = 'https://api-test.stateset.com';
  
  beforeEach(() => {
    client = new StateSetClient({ 
      apiKey,
      baseURL 
    });
    
    // Clean up any pending HTTP mocks
    nock.cleanAll();
  });
  
  afterEach(() => {
    nock.cleanAll();
  });
  
  describe('Orders API', () => {
    it('should create an order successfully', async () => {
      const orderData = {
        customer_email: 'test.customer@yourstore.com',
        items: [
          {
            sku: 'TEST-001',
            quantity: 2,
            price: 29.99
          }
        ],
        shipping_address: {
          street1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zip: '12345',
          country: 'US'
        }
      };
      
      const expectedResponse = {
        id: 'ord_test_123',
        status: 'pending',
        ...orderData
      };
      
      // Mock the API call
      nock(baseURL)
        .post('/v1/orders')
        .reply(201, expectedResponse);
      
      const result = await client.orders.create(orderData);
      
      expect(result).toEqual(expectedResponse);
      expect(result.id).toBe('ord_test_123');
      expect(result.status).toBe('pending');
    });
    
    it('should handle order creation errors', async () => {
      const invalidOrderData = {
        // Missing required fields
        items: []
      };
      
      nock(baseURL)
        .post('/v1/orders')
        .reply(400, {
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Missing required field: customer_email'
          }
        });
      
      await expect(client.orders.create(invalidOrderData))
        .rejects
        .toThrow('Missing required field: customer_email');
    });
    
    it('should list orders with pagination', async () => {
      const expectedOrders = {
        orders: [
          { id: 'ord_1', status: 'processing' },
          { id: 'ord_2', status: 'shipped' }
        ],
        total_count: 2,
        has_more: false
      };
      
      nock(baseURL)
        .get('/v1/orders')
        .query({ limit: 10, offset: 0 })
        .reply(200, expectedOrders);
      
      const result = await client.orders.list({ limit: 10, offset: 0 });
      
      expect(result.orders).toHaveLength(2);
      expect(result.total_count).toBe(2);
      expect(result.has_more).toBe(false);
    });
  });
  
  describe('Inventory API', () => {
    it('should update inventory levels', async () => {
      const inventoryUpdate = {
        sku: 'TEST-001',
        quantity: 100,
        location: 'warehouse_1'
      };
      
      const expectedResponse = {
        sku: 'TEST-001',
        available_quantity: 100,
        location: 'warehouse_1',
        updated_at: '2024-01-15T10:30:00Z'
      };
      
      nock(baseURL)
        .put('/v1/inventory/TEST-001')
        .reply(200, expectedResponse);
      
      const result = await client.inventory.update('TEST-001', inventoryUpdate);
      
      expect(result.available_quantity).toBe(100);
      expect(result.sku).toBe('TEST-001');
    });
    
    it('should handle insufficient inventory errors', async () => {
      nock(baseURL)
        .put('/v1/inventory/TEST-001')
        .reply(400, {
          error: {
            code: 'INSUFFICIENT_INVENTORY',
            message: 'Cannot reduce inventory below safety stock level'
          }
        });
      
      await expect(client.inventory.update('TEST-001', { quantity: -1000 }))
        .rejects
        .toThrow('Cannot reduce inventory below safety stock level');
    });
  });
  
  describe('Error Handling', () => {
    it('should retry on transient errors', async () => {
      nock(baseURL)
        .get('/v1/orders/ord_123')
        .reply(500, { error: 'Internal Server Error' })
        .get('/v1/orders/ord_123')
        .reply(500, { error: 'Internal Server Error' })
        .get('/v1/orders/ord_123')
        .reply(200, { id: 'ord_123', status: 'processing' });
      
      const result = await client.orders.get('ord_123');
      
      expect(result.id).toBe('ord_123');
      expect(result.status).toBe('processing');
    });
    
    it('should respect rate limits', async () => {
      nock(baseURL)
        .get('/v1/orders')
        .reply(429, {
          error: 'Rate limit exceeded',
          retry_after: 1
        })
        .get('/v1/orders')
        .reply(200, { orders: [], total_count: 0 });
      
      const startTime = Date.now();
      const result = await client.orders.list();
      const endTime = Date.now();
      
      // Should have waited at least 1 second
      expect(endTime - startTime).toBeGreaterThan(1000);
      expect(result.orders).toEqual([]);
    });
  });
});

Testing Business Logic

Test your business logic that uses the Stateset API:

// tests/unit/order-manager.test.js
import { OrderManager } from '../../src/services/order-manager';
import { StateSetClient } from 'stateset-node';

// Mock the entire Stateset client
jest.mock('stateset-node');

describe('OrderManager', () => {
  let orderManager;
  let mockClient;
  
  beforeEach(() => {
    mockClient = {
      orders: {
        create: jest.fn(),
        update: jest.fn(),
        get: jest.fn()
      },
      inventory: {
        allocate: jest.fn(),
        get: jest.fn()
      },
      shipping: {
        createLabel: jest.fn()
      }
    };
    
    StateSetClient.mockImplementation(() => mockClient);
    orderManager = new OrderManager('test-api-key');
  });
  
  describe('processOrder', () => {
    it('should process a complete order successfully', async () => {
      const orderData = {
        customer_email: 'test.customer@yourstore.com',
        items: [{ sku: 'TEST-001', quantity: 2 }]
      };
      
      // Mock successful API responses
      mockClient.orders.create.mockResolvedValue({
        id: 'ord_123',
        status: 'created'
      });
      
      mockClient.inventory.allocate.mockResolvedValue({
        allocated: true,
        items: [{ sku: 'TEST-001', quantity: 2, location: 'warehouse_1' }]
      });
      
      mockClient.orders.update.mockResolvedValue({
        id: 'ord_123',
        status: 'allocated'
      });
      
      mockClient.shipping.createLabel.mockResolvedValue({
        tracking_number: 'TRACK123',
        label_url: 'https://example.com/label.pdf'
      });
      
      const result = await orderManager.processOrder(orderData);
      
      expect(result.success).toBe(true);
      expect(result.order.id).toBe('ord_123');
      expect(result.tracking_number).toBe('TRACK123');
      
      // Verify the correct sequence of API calls
      expect(mockClient.orders.create).toHaveBeenCalledWith(orderData);
      expect(mockClient.inventory.allocate).toHaveBeenCalled();
      expect(mockClient.shipping.createLabel).toHaveBeenCalled();
    });
    
    it('should handle inventory allocation failures', async () => {
      const orderData = {
        customer_email: 'test.customer@yourstore.com',
        items: [{ sku: 'OUT-OF-STOCK', quantity: 10 }]
      };
      
      mockClient.orders.create.mockResolvedValue({
        id: 'ord_123',
        status: 'created'
      });
      
      mockClient.inventory.allocate.mockRejectedValue(
        new Error('Insufficient inventory for SKU: OUT-OF-STOCK')
      );
      
      mockClient.orders.update.mockResolvedValue({
        id: 'ord_123',
        status: 'allocation_failed'
      });
      
      const result = await orderManager.processOrder(orderData);
      
      expect(result.success).toBe(false);
      expect(result.error).toContain('Insufficient inventory');
      expect(mockClient.orders.update).toHaveBeenCalledWith('ord_123', {
        status: 'allocation_failed'
      });
    });
  });
  
  describe('calculateShippingCost', () => {
    it('should calculate shipping cost based on weight and distance', () => {
      const order = {
        items: [
          { weight: 1.5, quantity: 2 },
          { weight: 0.5, quantity: 1 }
        ],
        shipping_address: { country: 'US', state: 'CA' }
      };
      
      const cost = orderManager.calculateShippingCost(order);
      
      expect(cost).toBeGreaterThan(0);
      expect(typeof cost).toBe('number');
    });
    
    it('should apply free shipping for orders over threshold', () => {
      const order = {
        total_amount: 100,
        items: [{ weight: 1, quantity: 1 }],
        shipping_address: { country: 'US' }
      };
      
      const cost = orderManager.calculateShippingCost(order);
      
      expect(cost).toBe(0); // Free shipping over $75
    });
  });
});

Integration Testing

Testing API Endpoints

Test your API endpoints with real HTTP requests:

// tests/integration/orders-api.test.js
import request from 'supertest';
import app from '../../src/app';
import { setupTestDatabase, teardownTestDatabase } from '../helpers/database';

describe('Orders API Integration', () => {
  beforeAll(async () => {
    await setupTestDatabase();
  });
  
  afterAll(async () => {
    await teardownTestDatabase();
  });
  
  describe('POST /api/orders', () => {
    it('should create a new order', async () => {
      const orderData = {
        customer_email: 'integration@test.com',
        items: [
          {
            sku: 'INTEGRATION-001',
            quantity: 1,
            price: 49.99
          }
        ],
        shipping_address: {
          street1: '123 Integration St',
          city: 'Test City',
          state: 'TS',
          zip: '12345',
          country: 'US'
        }
      };
      
      const response = await request(app)
        .post('/api/orders')
        .send(orderData)
        .expect(201);
      
      expect(response.body).toHaveProperty('id');
      expect(response.body.status).toBe('pending');
      expect(response.body.customer_email).toBe('integration@test.com');
      
      // Verify order was created in Stateset
      const createdOrder = await request(app)
        .get(`/api/orders/${response.body.id}`)
        .expect(200);
      
      expect(createdOrder.body.id).toBe(response.body.id);
    });
    
    it('should validate required fields', async () => {
      const invalidOrder = {
        items: [] // Missing customer_email and valid items
      };
      
      const response = await request(app)
        .post('/api/orders')
        .send(invalidOrder)
        .expect(400);
      
      expect(response.body.error).toContain('customer_email is required');
    });
  });
  
  describe('GET /api/orders', () => {
    beforeEach(async () => {
      // Create test orders
      await request(app)
        .post('/api/orders')
        .send({
          customer_email: 'test1@example.com',
          items: [{ sku: 'TEST-001', quantity: 1, price: 10 }]
        });
      
      await request(app)
        .post('/api/orders')
        .send({
          customer_email: 'test2@example.com',
          items: [{ sku: 'TEST-002', quantity: 2, price: 20 }]
        });
    });
    
    it('should list orders with pagination', async () => {
      const response = await request(app)
        .get('/api/orders?limit=1&offset=0')
        .expect(200);
      
      expect(response.body.orders).toHaveLength(1);
      expect(response.body).toHaveProperty('total_count');
      expect(response.body).toHaveProperty('has_more');
    });
    
    it('should filter orders by status', async () => {
      const response = await request(app)
        .get('/api/orders?status=pending')
        .expect(200);
      
      expect(response.body.orders.every(order => order.status === 'pending')).toBe(true);
    });
    
    it('should search orders by customer email', async () => {
      const response = await request(app)
        .get('/api/orders?customer_email=test1@example.com')
        .expect(200);
      
      expect(response.body.orders).toHaveLength(1);
      expect(response.body.orders[0].customer_email).toBe('test1@example.com');
    });
  });
});

Testing Webhook Handlers

Test webhook event processing:

// tests/integration/webhooks.test.js
import request from 'supertest';
import crypto from 'crypto';
import app from '../../src/app';

describe('Webhook Integration', () => {
  const webhookSecret = 'test_webhook_secret';
  
  function signWebhookPayload(payload) {
    const signature = crypto
      .createHmac('sha256', webhookSecret)
      .update(JSON.stringify(payload))
      .digest('hex');
    return `sha256=${signature}`;
  }
  
  describe('POST /webhooks/stateset', () => {
    it('should process order.created webhook', async () => {
      const webhookPayload = {
        event: 'order.created',
        data: {
          id: 'ord_webhook_test',
          status: 'pending',
          customer_email: 'webhook@test.com',
          total_amount: 99.99
        }
      };
      
      const signature = signWebhookPayload(webhookPayload);
      
      const response = await request(app)
        .post('/webhooks/stateset')
        .set('X-Stateset-Signature', signature)
        .send(webhookPayload)
        .expect(200);
      
      expect(response.body.received).toBe(true);
      
      // Verify the webhook was processed
      // Check database, logs, or external service calls
    });
    
    it('should reject webhooks with invalid signatures', async () => {
      const webhookPayload = {
        event: 'order.created',
        data: { id: 'ord_test' }
      };
      
      await request(app)
        .post('/webhooks/stateset')
        .set('X-Stateset-Signature', 'invalid-signature')
        .send(webhookPayload)
        .expect(401);
    });
    
    it('should handle unknown webhook events gracefully', async () => {
      const webhookPayload = {
        event: 'unknown.event',
        data: { id: 'test' }
      };
      
      const signature = signWebhookPayload(webhookPayload);
      
      const response = await request(app)
        .post('/webhooks/stateset')
        .set('X-Stateset-Signature', signature)
        .send(webhookPayload)
        .expect(200);
      
      expect(response.body.received).toBe(true);
    });
  });
});

End-to-End Testing

Complete Order Workflow Testing

Test the entire order lifecycle from creation to fulfillment:

// tests/e2e/order-lifecycle.test.js
import { StateSetClient } from 'stateset-node';
import { faker } from '@faker-js/faker';

describe('Order Lifecycle E2E', () => {
  let client;
  let testOrder;
  
  beforeAll(() => {
    client = new StateSetClient({
      apiKey: process.env.STATESET_TEST_API_KEY,
      environment: 'test'
    });
  });
  
  describe('Complete Order Flow', () => {
    it('should process order from creation to delivery', async () => {
      // Step 1: Create order
      const orderData = {
        customer_email: faker.internet.email(),
        customer_name: faker.person.fullName(),
        items: [
          {
            sku: 'E2E-TEST-001',
            quantity: 2,
            price: 25.99,
            name: 'Test Product'
          }
        ],
        shipping_address: {
          name: faker.person.fullName(),
          street1: faker.location.streetAddress(),
          city: faker.location.city(),
          state: faker.location.state({ abbreviated: true }),
          zip: faker.location.zipCode(),
          country: 'US'
        },
        payment_method: {
          type: 'test_card',
          token: 'tok_test_visa'
        }
      };
      
      testOrder = await client.orders.create(orderData);
      
      expect(testOrder.id).toBeDefined();
      expect(testOrder.status).toBe('pending');
      
      // Step 2: Wait for payment processing
      await waitForOrderStatus(testOrder.id, 'payment_confirmed', 30000);
      
      // Step 3: Allocate inventory
      const allocation = await client.inventory.allocate({
        order_id: testOrder.id,
        items: orderData.items
      });
      
      expect(allocation.success).toBe(true);
      expect(allocation.items).toHaveLength(1);
      
      // Step 4: Create fulfillment
      const fulfillment = await client.fulfillment.create({
        order_id: testOrder.id,
        location_id: 'test_warehouse',
        items: orderData.items
      });
      
      expect(fulfillment.id).toBeDefined();
      expect(fulfillment.status).toBe('pending');
      
      // Step 5: Generate shipping label
      const shippingLabel = await client.shipping.createLabel({
        fulfillment_id: fulfillment.id,
        carrier: 'test_carrier',
        service: 'ground'
      });
      
      expect(shippingLabel.tracking_number).toBeDefined();
      expect(shippingLabel.label_url).toBeDefined();
      
      // Step 6: Mark as shipped
      const shippedOrder = await client.orders.update(testOrder.id, {
        status: 'shipped',
        tracking_number: shippingLabel.tracking_number
      });
      
      expect(shippedOrder.status).toBe('shipped');
      expect(shippedOrder.tracking_number).toBe(shippingLabel.tracking_number);
      
      // Step 7: Simulate delivery
      const deliveredOrder = await client.orders.update(testOrder.id, {
        status: 'delivered',
        delivered_at: new Date().toISOString()
      });
      
      expect(deliveredOrder.status).toBe('delivered');
      expect(deliveredOrder.delivered_at).toBeDefined();
      
    }, 60000); // 60 second timeout for complete flow
    
    async function waitForOrderStatus(orderId, expectedStatus, timeout = 10000) {
      const startTime = Date.now();
      
      while (Date.now() - startTime < timeout) {
        const order = await client.orders.get(orderId);
        
        if (order.status === expectedStatus) {
          return order;
        }
        
        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
      }
      
      throw new Error(`Order ${orderId} did not reach status ${expectedStatus} within ${timeout}ms`);
    }
  });
  
  describe('Return Processing', () => {
    it('should process return for delivered order', async () => {
      // Ensure we have a delivered order
      expect(testOrder.status).toBe('delivered');
      
      // Step 1: Create return
      const returnData = {
        order_id: testOrder.id,
        items: [
          {
            sku: 'E2E-TEST-001',
            quantity: 1,
            reason: 'defective',
            condition: 'damaged'
          }
        ],
        reason_code: 'defective',
        customer_notes: 'Product arrived damaged'
      };
      
      const returnRecord = await client.returns.create(returnData);
      
      expect(returnRecord.id).toBeDefined();
      expect(returnRecord.status).toBe('pending');
      
      // Step 2: Generate return label
      const returnLabel = await client.shipping.createReturnLabel({
        return_id: returnRecord.id,
        carrier: 'test_carrier'
      });
      
      expect(returnLabel.tracking_number).toBeDefined();
      
      // Step 3: Process return receipt
      const receivedReturn = await client.returns.update(returnRecord.id, {
        status: 'received',
        received_at: new Date().toISOString()
      });
      
      expect(receivedReturn.status).toBe('received');
      
      // Step 4: Process refund
      const refund = await client.refunds.create({
        return_id: returnRecord.id,
        amount: 25.99,
        reason: 'defective_product'
      });
      
      expect(refund.id).toBeDefined();
      expect(refund.amount).toBe(25.99);
      expect(refund.status).toBe('processed');
    });
  });
});

Performance Testing

Load Testing

Test API performance under load:

// tests/performance/load-test.js
import { StateSetClient } from 'stateset-node';
import { performance } from 'perf_hooks';

describe('Load Testing', () => {
  let client;
  
  beforeAll(() => {
    client = new StateSetClient({
      apiKey: process.env.STATESET_TEST_API_KEY,
      environment: 'test'
    });
  });
  
  describe('Order Creation Performance', () => {
    it('should handle 100 concurrent order creations', async () => {
      const concurrentRequests = 100;
      const orderPromises = [];
      
      const startTime = performance.now();
      
      for (let i = 0; i < concurrentRequests; i++) {
        const orderData = {
          customer_email: `load-test-${i}@example.com`,
          items: [
            {
              sku: 'LOAD-TEST-001',
              quantity: 1,
              price: 10.00
            }
          ]
        };
        
        orderPromises.push(client.orders.create(orderData));
      }
      
      const results = await Promise.allSettled(orderPromises);
      const endTime = performance.now();
      
      const successfulRequests = results.filter(r => r.status === 'fulfilled').length;
      const failedRequests = results.filter(r => r.status === 'rejected').length;
      const totalTime = endTime - startTime;
      const averageTime = totalTime / concurrentRequests;
      
      console.log(`Performance Results:
        - Total requests: ${concurrentRequests}
        - Successful: ${successfulRequests}
        - Failed: ${failedRequests}
        - Total time: ${totalTime.toFixed(2)}ms
        - Average time per request: ${averageTime.toFixed(2)}ms
        - Requests per second: ${(concurrentRequests / (totalTime / 1000)).toFixed(2)}`);
      
      // Assert performance requirements
      expect(successfulRequests).toBeGreaterThan(concurrentRequests * 0.95); // 95% success rate
      expect(averageTime).toBeLessThan(1000); // Average response time under 1 second
      
    }, 30000); // 30 second timeout
  });
  
  describe('API Rate Limiting', () => {
    it('should handle rate limiting gracefully', async () => {
      const requests = [];
      const startTime = performance.now();
      
      // Make many requests quickly to trigger rate limiting
      for (let i = 0; i < 200; i++) {
        requests.push(
          client.orders.list({ limit: 1 })
            .catch(error => ({ error: error.message }))
        );
      }
      
      const results = await Promise.all(requests);
      const endTime = performance.now();
      
      const successful = results.filter(r => !r.error).length;
      const rateLimited = results.filter(r => 
        r.error && r.error.includes('rate limit')
      ).length;
      
      console.log(`Rate Limiting Results:
        - Total requests: ${requests.length}
        - Successful: ${successful}
        - Rate limited: ${rateLimited}
        - Time taken: ${(endTime - startTime).toFixed(2)}ms`);
      
      expect(rateLimited).toBeGreaterThan(0); // Should encounter rate limiting
      expect(successful).toBeGreaterThan(0); // Some requests should succeed
    });
  });
});

Test Data Management

Test Data Factory

Create realistic test data:

// tests/helpers/test-data-factory.js
import { faker } from '@faker-js/faker';

export class TestDataFactory {
  static createOrderData(overrides = {}) {
    return {
      customer_email: faker.internet.email(),
      customer_name: faker.person.fullName(),
      items: [
        {
          sku: faker.string.alphanumeric(8).toUpperCase(),
          quantity: faker.number.int({ min: 1, max: 5 }),
          price: parseFloat(faker.commerce.price()),
          name: faker.commerce.productName()
        }
      ],
      shipping_address: this.createAddress(),
      billing_address: this.createAddress(),
      ...overrides
    };
  }
  
  static createAddress() {
    return {
      name: faker.person.fullName(),
      company: faker.company.name(),
      street1: faker.location.streetAddress(),
      street2: faker.location.secondaryAddress(),
      city: faker.location.city(),
      state: faker.location.state({ abbreviated: true }),
      zip: faker.location.zipCode(),
      country: faker.location.countryCode('alpha-2'),
      phone: faker.phone.number()
    };
  }
  
  static createInventoryItem(overrides = {}) {
    return {
      sku: faker.string.alphanumeric(8).toUpperCase(),
      name: faker.commerce.productName(),
      description: faker.commerce.productDescription(),
      price: parseFloat(faker.commerce.price()),
      cost: parseFloat(faker.commerce.price({ min: 5, max: 50 })),
      weight: faker.number.float({ min: 0.1, max: 5.0 }),
      category: faker.commerce.department(),
      quantity: faker.number.int({ min: 0, max: 1000 }),
      ...overrides
    };
  }
  
  static createCustomer(overrides = {}) {
    return {
      email: faker.internet.email(),
      name: faker.person.fullName(),
      phone: faker.phone.number(),
      address: this.createAddress(),
      tier: faker.helpers.arrayElement(['bronze', 'silver', 'gold', 'platinum']),
      ...overrides
    };
  }
}

Database Setup for Testing

// tests/helpers/database.js
import { Pool } from 'pg';
import { migrate } from 'postgres-migrations';

const testDbConfig = {
  host: 'localhost',
  port: 5432,
  database: 'stateset_test',
  user: 'test_user',
  password: 'test_password'
};

let pool;

export async function setupTestDatabase() {
  pool = new Pool(testDbConfig);
  
  // Run migrations
  await migrate(testDbConfig, 'migrations');
  
  // Seed test data
  await seedTestData();
}

export async function teardownTestDatabase() {
  if (pool) {
    await pool.end();
  }
}

export async function cleanDatabase() {
  const tables = [
    'orders',
    'order_line_items',
    'inventory',
    'customers',
    'returns',
    'fulfillments'
  ];
  
  for (const table of tables) {
    await pool.query(`TRUNCATE TABLE ${table} CASCADE`);
  }
}

async function seedTestData() {
  // Insert test inventory items
  await pool.query(`
    INSERT INTO inventory (sku, name, quantity, price) VALUES
    ('TEST-001', 'Test Product 1', 100, 25.99),
    ('TEST-002', 'Test Product 2', 50, 49.99),
    ('E2E-TEST-001', 'E2E Test Product', 1000, 25.99)
  `);
  
  // Insert test customers
  await pool.query(`
    INSERT INTO customers (email, name, tier) VALUES
    ('test@example.com', 'Test Customer', 'silver'),
    ('vip@example.com', 'VIP Customer', 'platinum')
  `);
}

Continuous Integration

GitHub Actions Workflow

# .github/workflows/api-tests.yml
name: API Tests

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: test_password
          POSTGRES_USER: test_user
          POSTGRES_DB: stateset_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    
    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: Setup test environment
      run: |
        cp .env.test.example .env.test
      env:
        STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
        DATABASE_URL: postgresql://test_user:test_password@localhost:5432/stateset_test
    
    - name: Run unit tests
      run: npm run test:unit
    
    - name: Run integration tests
      run: npm run test:integration
    
    - name: Run E2E tests
      run: npm run test:e2e
    
    - name: Run performance tests
      run: npm run test:performance
    
    - name: Upload coverage reports
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

Best Practices

Testing Strategy

  1. Test Pyramid: More unit tests, fewer integration tests, minimal E2E tests
  2. Isolation: Each test should be independent and not rely on others
  3. Deterministic: Tests should produce consistent results
  4. Fast Feedback: Unit tests should run quickly for rapid development

Test Organization

  1. Naming: Use descriptive test names that explain what is being tested
  2. Structure: Group related tests using describe blocks
  3. Setup/Teardown: Use beforeEach/afterEach for consistent test state
  4. Data Management: Use factories for generating test data

Mocking Guidelines

  1. Mock External Services: Don’t make real API calls in unit tests
  2. Test Behavior: Mock what you control, test what you own
  3. Realistic Mocks: Make mocks behave like the real service
  4. Verification: Verify that mocks are called with correct parameters

Performance Considerations

  1. Parallel Execution: Run tests in parallel when possible
  2. Resource Management: Clean up resources properly
  3. Test Data: Use minimal test data sets
  4. Timeouts: Set appropriate timeouts for async operations

Troubleshooting

Next Steps

Conclusion

You now have a comprehensive testing strategy for your Stateset API integration that includes:

  • ✅ Unit testing for individual components
  • ✅ Integration testing for API endpoints
  • ✅ End-to-end testing for complete workflows
  • ✅ Performance testing for load scenarios
  • ✅ Proper test data management
  • ✅ CI/CD integration
  • ✅ Best practices and troubleshooting

This testing approach ensures your integration is reliable, performant, and maintainable.