Complete guide to testing StateSet API integrations including unit tests, integration tests, and end-to-end testing strategies
// jest.config.js
module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
testMatch: [
'**/__tests__/**/*.test.js',
'**/*.test.js'
],
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
testTimeout: 30000
};
// tests/setup.js
import dotenv from 'dotenv';
import { jest } from '@jest/globals';
// Load test environment variables
dotenv.config({ path: '.env.test' });
// Global test setup
beforeAll(() => {
// Mock console methods in tests
global.console = {
...console,
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
};
});
afterAll(() => {
// Cleanup after all tests
jest.restoreAllMocks();
});
// src/orderProcessor.js
import { StateSetClient } from 'stateset-node';
import winston from 'winston';
export class OrderProcessor {
constructor(client, logger = winston.createLogger()) {
this.client = client;
this.logger = logger;
}
async processOrder(orderData) {
try {
// Validate order data
const validation = this.validateOrder(orderData);
if (!validation.isValid) {
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}
// Create order
const order = await this.client.orders.create(orderData);
this.logger.info('Order processed successfully', { orderId: order.id });
return order;
} catch (error) {
this.logger.error('Order processing failed', { error: error.message });
throw error;
}
}
validateOrder(orderData) {
const errors = [];
if (!orderData.customer_email) {
errors.push('Customer email is required');
}
if (!orderData.items || orderData.items.length === 0) {
errors.push('Order must contain at least one item');
}
if (orderData.items) {
orderData.items.forEach((item, index) => {
if (!item.sku) errors.push(`Item ${index}: SKU is required`);
if (!item.quantity || item.quantity <= 0) {
errors.push(`Item ${index}: Quantity must be positive`);
}
});
}
return {
isValid: errors.length === 0,
errors
};
}
}
// __tests__/orderProcessor.test.js
import { OrderProcessor } from '../src/orderProcessor.js';
import { jest } from '@jest/globals';
describe('OrderProcessor', () => {
let orderProcessor;
let mockClient;
let mockLogger;
beforeEach(() => {
mockClient = {
orders: {
create: jest.fn()
}
};
mockLogger = {
info: jest.fn(),
error: jest.fn()
};
orderProcessor = new OrderProcessor(mockClient, mockLogger);
});
describe('validateOrder', () => {
it('should return valid for correct order data', () => {
const orderData = {
customer_email: 'test@example.com',
items: [
{ sku: 'ITEM-001', quantity: 2 }
]
};
const result = orderProcessor.validateOrder(orderData);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should return invalid for missing email', () => {
const orderData = {
items: [{ sku: 'ITEM-001', quantity: 2 }]
};
const result = orderProcessor.validateOrder(orderData);
expect(result.isValid).toBe(false);
expect(result.errors).toContain('Customer email is required');
});
it('should return invalid for empty items', () => {
const orderData = {
customer_email: 'test@example.com',
items: []
};
const result = orderProcessor.validateOrder(orderData);
expect(result.isValid).toBe(false);
expect(result.errors).toContain('Order must contain at least one item');
});
it('should validate individual items', () => {
const orderData = {
customer_email: 'test@example.com',
items: [
{ sku: 'ITEM-001', quantity: 2 },
{ quantity: 1 }, // Missing SKU
{ sku: 'ITEM-003', quantity: -1 } // Invalid quantity
]
};
const result = orderProcessor.validateOrder(orderData);
expect(result.isValid).toBe(false);
expect(result.errors).toContain('Item 1: SKU is required');
expect(result.errors).toContain('Item 2: Quantity must be positive');
});
});
describe('processOrder', () => {
it('should create order successfully', async () => {
const orderData = {
customer_email: 'test@example.com',
items: [{ sku: 'ITEM-001', quantity: 2 }]
};
const expectedOrder = { id: 'order_123', ...orderData };
mockClient.orders.create.mockResolvedValue(expectedOrder);
const result = await orderProcessor.processOrder(orderData);
expect(mockClient.orders.create).toHaveBeenCalledWith(orderData);
expect(mockLogger.info).toHaveBeenCalledWith(
'Order processed successfully',
{ orderId: 'order_123' }
);
expect(result).toEqual(expectedOrder);
});
it('should throw error for invalid order data', async () => {
const orderData = { items: [] }; // Invalid data
await expect(orderProcessor.processOrder(orderData)).rejects.toThrow(
'Validation failed'
);
expect(mockClient.orders.create).not.toHaveBeenCalled();
expect(mockLogger.error).toHaveBeenCalled();
});
it('should handle API errors', async () => {
const orderData = {
customer_email: 'test@example.com',
items: [{ sku: 'ITEM-001', quantity: 2 }]
};
const apiError = new Error('API Error');
mockClient.orders.create.mockRejectedValue(apiError);
await expect(orderProcessor.processOrder(orderData)).rejects.toThrow('API Error');
expect(mockLogger.error).toHaveBeenCalledWith(
'Order processing failed',
{ error: 'API Error' }
);
});
});
});
// __tests__/integration/orders.integration.test.js
import { StateSetClient } from 'stateset-node';
import { OrderProcessor } from '../../src/orderProcessor.js';
describe('Orders Integration Tests', () => {
let client;
let orderProcessor;
let createdOrderIds = [];
beforeAll(() => {
client = new StateSetClient({
apiKey: process.env.STATESET_TEST_API_KEY,
environment: 'sandbox'
});
orderProcessor = new OrderProcessor(client);
});
afterAll(async () => {
// Cleanup created orders
for (const orderId of createdOrderIds) {
try {
await client.orders.delete(orderId);
} catch (error) {
console.warn(`Failed to cleanup order ${orderId}:`, error.message);
}
}
});
it('should create and retrieve an order', async () => {
const orderData = {
customer_email: 'integration-test@example.com',
items: [
{
sku: 'TEST-ITEM-001',
quantity: 2,
price: 1999
}
],
shipping_address: {
line1: '123 Test Street',
city: 'Test City',
state: 'CA',
postal_code: '90210',
country: 'US'
}
};
// Create order
const createdOrder = await orderProcessor.processOrder(orderData);
createdOrderIds.push(createdOrder.id);
expect(createdOrder.id).toBeDefined();
expect(createdOrder.customer_email).toBe(orderData.customer_email);
expect(createdOrder.status).toBe('pending');
// Retrieve order
const retrievedOrder = await client.orders.get(createdOrder.id);
expect(retrievedOrder.id).toBe(createdOrder.id);
expect(retrievedOrder.customer_email).toBe(orderData.customer_email);
});
it('should handle order status updates', async () => {
const orderData = {
customer_email: 'status-test@example.com',
items: [{ sku: 'TEST-ITEM-002', quantity: 1, price: 999 }],
shipping_address: {
line1: '456 Status Street',
city: 'Status City',
state: 'NY',
postal_code: '10001',
country: 'US'
}
};
const order = await orderProcessor.processOrder(orderData);
createdOrderIds.push(order.id);
// Update order status
const updatedOrder = await client.orders.update(order.id, {
status: 'confirmed'
});
expect(updatedOrder.status).toBe('confirmed');
// Verify status change
const retrievedOrder = await client.orders.get(order.id);
expect(retrievedOrder.status).toBe('confirmed');
});
it('should handle order cancellation', async () => {
const orderData = {
customer_email: 'cancel-test@example.com',
items: [{ sku: 'TEST-ITEM-003', quantity: 1, price: 1500 }],
shipping_address: {
line1: '789 Cancel Street',
city: 'Cancel City',
state: 'TX',
postal_code: '75001',
country: 'US'
}
};
const order = await orderProcessor.processOrder(orderData);
createdOrderIds.push(order.id);
// Cancel order
const cancelledOrder = await client.orders.cancel(order.id, {
reason: 'Customer requested cancellation'
});
expect(cancelledOrder.status).toBe('cancelled');
expect(cancelledOrder.cancellation_reason).toBe('Customer requested cancellation');
});
it('should handle API errors gracefully', async () => {
const invalidOrderData = {
customer_email: 'invalid-test@example.com',
items: [
{
sku: 'INVALID-SKU-THAT-DOES-NOT-EXIST',
quantity: 1,
price: 999
}
]
};
await expect(orderProcessor.processOrder(invalidOrderData)).rejects.toThrow();
});
it('should handle rate limiting', async () => {
const requests = [];
// Make multiple concurrent requests to test rate limiting
for (let i = 0; i < 10; i++) {
requests.push(
client.orders.list({ limit: 1 })
);
}
const results = await Promise.allSettled(requests);
// At least some requests should succeed
const successful = results.filter(r => r.status === 'fulfilled');
expect(successful.length).toBeGreaterThan(0);
// Check if any were rate limited
const rateLimited = results.filter(
r => r.status === 'rejected' && r.reason.status === 429
);
if (rateLimited.length > 0) {
console.log(`${rateLimited.length} requests were rate limited (expected behavior)`);
}
});
});
// tests/e2e/order-workflow.e2e.test.js
import { test, expect } from '@playwright/test';
import { StateSetClient } from 'stateset-node';
test.describe('Complete Order Workflow', () => {
let client;
let testOrderId;
test.beforeAll(async () => {
client = new StateSetClient({
apiKey: process.env.STATESET_TEST_API_KEY,
environment: 'sandbox'
});
});
test.afterAll(async () => {
// Cleanup
if (testOrderId) {
try {
await client.orders.delete(testOrderId);
} catch (error) {
console.warn('Failed to cleanup test order:', error.message);
}
}
});
test('should complete full order lifecycle', async ({ page }) => {
// 1. Create order via API
const orderData = {
customer_email: 'e2e-test@example.com',
items: [
{
sku: 'E2E-TEST-ITEM',
quantity: 1,
price: 2999,
name: 'E2E Test Product'
}
],
shipping_address: {
line1: '123 E2E Street',
city: 'Test City',
state: 'CA',
postal_code: '90210',
country: 'US'
}
};
const order = await client.orders.create(orderData);
testOrderId = order.id;
expect(order.status).toBe('pending');
// 2. Navigate to order management dashboard
await page.goto(`${process.env.DASHBOARD_URL}/orders/${order.id}`);
// 3. Verify order details in UI
await expect(page.locator('[data-testid="order-id"]')).toContainText(order.id);
await expect(page.locator('[data-testid="customer-email"]')).toContainText('e2e-test@example.com');
await expect(page.locator('[data-testid="order-status"]')).toContainText('pending');
// 4. Process order through UI
await page.click('[data-testid="confirm-order-btn"]');
await page.waitForSelector('[data-testid="order-status"]:has-text("confirmed")');
// 5. Verify status change via API
const updatedOrder = await client.orders.get(order.id);
expect(updatedOrder.status).toBe('confirmed');
// 6. Ship order
await page.click('[data-testid="ship-order-btn"]');
await page.fill('[data-testid="tracking-number"]', 'TEST-TRACKING-123');
await page.click('[data-testid="confirm-shipment-btn"]');
await page.waitForSelector('[data-testid="order-status"]:has-text("shipped")');
// 7. Verify final status
const shippedOrder = await client.orders.get(order.id);
expect(shippedOrder.status).toBe('shipped');
expect(shippedOrder.tracking_number).toBe('TEST-TRACKING-123');
// 8. Check customer notification
const notifications = await client.notifications.list({
order_id: order.id,
type: 'shipment'
});
expect(notifications.data.length).toBeGreaterThan(0);
expect(notifications.data[0].recipient_email).toBe('e2e-test@example.com');
});
test('should handle order cancellation workflow', async ({ page }) => {
// Create order
const orderData = {
customer_email: 'cancel-e2e-test@example.com',
items: [{ sku: 'CANCEL-TEST-ITEM', quantity: 1, price: 1999 }],
shipping_address: {
line1: '456 Cancel Street',
city: 'Cancel City',
state: 'NY',
postal_code: '10001',
country: 'US'
}
};
const order = await client.orders.create(orderData);
testOrderId = order.id;
// Navigate to order
await page.goto(`${process.env.DASHBOARD_URL}/orders/${order.id}`);
// Cancel order through UI
await page.click('[data-testid="cancel-order-btn"]');
await page.fill('[data-testid="cancellation-reason"]', 'Customer requested cancellation');
await page.click('[data-testid="confirm-cancellation-btn"]');
// Wait for status update
await page.waitForSelector('[data-testid="order-status"]:has-text("cancelled")');
// Verify via API
const cancelledOrder = await client.orders.get(order.id);
expect(cancelledOrder.status).toBe('cancelled');
expect(cancelledOrder.cancellation_reason).toBe('Customer requested cancellation');
});
});
# artillery-config.yml
config:
target: 'https://api.sandbox.stateset.com'
phases:
- duration: 60
arrivalRate: 5
name: "Warm up"
- duration: 120
arrivalRate: 10
name: "Load test"
- duration: 60
arrivalRate: 20
name: "Stress test"
variables:
api_key: "{{ $env.STATESET_TEST_API_KEY }}"
processor: "./test-helpers.js"
scenarios:
- name: "Order Management"
weight: 70
flow:
- post:
url: "/v1/orders"
headers:
Authorization: "Bearer {{ api_key }}"
Content-Type: "application/json"
json:
customer_email: "load-test-{{ $randomString() }}@example.com"
items:
- sku: "LOAD-TEST-ITEM"
quantity: "{{ $randomInt(1, 5) }}"
price: 1999
shipping_address:
line1: "123 Load Test St"
city: "Test City"
state: "CA"
postal_code: "90210"
country: "US"
capture:
- json: "$.id"
as: "orderId"
- get:
url: "/v1/orders/{{ orderId }}"
headers:
Authorization: "Bearer {{ api_key }}"
- put:
url: "/v1/orders/{{ orderId }}"
headers:
Authorization: "Bearer {{ api_key }}"
Content-Type: "application/json"
json:
status: "confirmed"
- name: "Order Listing"
weight: 30
flow:
- get:
url: "/v1/orders"
qs:
limit: "{{ $randomInt(10, 50) }}"
status: "pending"
headers:
Authorization: "Bearer {{ api_key }}"
// stress-test.js
import { StateSetClient } from 'stateset-node';
import { performance } from 'perf_hooks';
class StressTest {
constructor() {
this.client = new StateSetClient({
apiKey: process.env.STATESET_TEST_API_KEY,
environment: 'sandbox'
});
this.metrics = {
requests: 0,
errors: 0,
latencies: []
};
}
async runConcurrentOrders(concurrency = 10, duration = 60000) {
console.log(`Starting stress test: ${concurrency} concurrent users for ${duration}ms`);
const startTime = Date.now();
const workers = [];
// Start concurrent workers
for (let i = 0; i < concurrency; i++) {
workers.push(this.orderWorker(startTime + duration));
}
// Wait for all workers to complete
await Promise.all(workers);
// Calculate results
const totalDuration = Date.now() - startTime;
const avgLatency = this.metrics.latencies.reduce((a, b) => a + b, 0) / this.metrics.latencies.length;
const errorRate = (this.metrics.errors / this.metrics.requests) * 100;
const throughput = this.metrics.requests / (totalDuration / 1000);
console.log('Stress Test Results:');
console.log(`Total Requests: ${this.metrics.requests}`);
console.log(`Errors: ${this.metrics.errors} (${errorRate.toFixed(2)}%)`);
console.log(`Average Latency: ${avgLatency.toFixed(2)}ms`);
console.log(`Throughput: ${throughput.toFixed(2)} req/sec`);
return {
requests: this.metrics.requests,
errors: this.metrics.errors,
errorRate,
avgLatency,
throughput
};
}
async orderWorker(endTime) {
while (Date.now() < endTime) {
const start = performance.now();
try {
await this.createTestOrder();
this.metrics.requests++;
this.metrics.latencies.push(performance.now() - start);
} catch (error) {
this.metrics.errors++;
console.error('Order creation failed:', error.message);
}
// Small delay to prevent overwhelming
await new Promise(resolve => setTimeout(resolve, 100));
}
}
async createTestOrder() {
const orderData = {
customer_email: `stress-test-${Date.now()}@example.com`,
items: [
{
sku: 'STRESS-TEST-ITEM',
quantity: Math.floor(Math.random() * 5) + 1,
price: Math.floor(Math.random() * 5000) + 1000
}
],
shipping_address: {
line1: '123 Stress Test Street',
city: 'Test City',
state: 'CA',
postal_code: '90210',
country: 'US'
}
};
return this.client.orders.create(orderData);
}
}
// Run stress test
const stressTest = new StressTest();
stressTest.runConcurrentOrders(20, 120000) // 20 concurrent users for 2 minutes
.then(results => {
console.log('Stress test completed successfully');
process.exit(0);
})
.catch(error => {
console.error('Stress test failed:', error);
process.exit(1);
});
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run test:unit
env:
STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
- name: Upload coverage reports
uses: codecov/codecov-action@v3
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run test:integration
env:
STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
e2e-tests:
runs-on: ubuntu-latest
needs: integration-tests
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npx playwright install
- run: npm run test:e2e
env:
STATESET_TEST_API_KEY: ${{ secrets.STATESET_TEST_API_KEY }}
DASHBOARD_URL: ${{ secrets.DASHBOARD_URL }}