Comprehensive Testing Guide
Testing is crucial for building reliable StateSet integrations. This guide covers testing strategies from unit tests to end-to-end scenarios, helping you build confidence in your integration.Testing Overview
Unit Testing
Test individual functions and components in isolation
Integration Testing
Test interactions between your code and StateSet APIs
End-to-End Testing
Test complete workflows from start to finish
Test Environment Setup
Testing Pyramid Strategy
Environment Configuration
- Node.js / Jest
- Python / pytest
- Ruby / RSpec
Copy
// 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();
});
Copy
# pytest.ini
[tool:pytest]
minversion = 6.0
addopts =
-ra
--strict-markers
--strict-config
--cov=src
--cov-report=term-missing
--cov-report=html
--cov-fail-under=80
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# conftest.py
import pytest
import os
from unittest.mock import Mock
from stateset import StateSetClient
@pytest.fixture(scope="session")
def test_config():
"""Test configuration fixture."""
return {
'api_key': os.getenv('STATESET_TEST_API_KEY', 'sk_test_fake_key'),
'base_url': 'https://api.sandbox.stateset.com',
'timeout': 30
}
@pytest.fixture
def mock_client():
"""Mock StateSet client for unit tests."""
return Mock(spec=StateSetClient)
@pytest.fixture
def real_client(test_config):
"""Real StateSet client for integration tests."""
return StateSetClient(**test_config)
@pytest.fixture(autouse=True)
def cleanup_test_data():
"""Cleanup test data after each test."""
yield
# Add cleanup logic here
pass
Copy
# spec/spec_helper.rb
require 'bundler/setup'
require 'stateset'
require 'webmock/rspec'
require 'vcr'
# Configure VCR for HTTP recording
VCR.configure do |config|
config.cassette_library_dir = 'spec/vcr_cassettes'
config.hook_into :webmock
config.configure_rspec_metadata!
config.filter_sensitive_data('<STATESET_API_KEY>') { ENV['STATESET_API_KEY'] }
end
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
# Test environment setup
config.before(:suite) do
Stateset.configure do |stateset_config|
stateset_config.api_key = ENV['STATESET_TEST_API_KEY'] || 'sk_test_fake'
stateset_config.environment = 'test'
end
end
end
Unit Testing
Testing Individual Functions
- Node.js
- Python
Copy
// 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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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' }
);
});
});
});
Copy
# src/order_processor.py
import logging
from typing import Dict, List, Any
from stateset import StateSetClient
class OrderProcessor:
def __init__(self, client: StateSetClient, logger=None):
self.client = client
self.logger = logger or logging.getLogger(__name__)
async def process_order(self, order_data: Dict[str, Any]) -> Dict[str, Any]:
try:
# Validate order data
validation = self.validate_order(order_data)
if not validation['is_valid']:
raise ValueError(f"Validation failed: {', '.join(validation['errors'])}")
# Create order
order = await self.client.orders.create(order_data)
self.logger.info(f'Order processed successfully: {order["id"]}')
return order
except Exception as error:
self.logger.error(f'Order processing failed: {str(error)}')
raise
def validate_order(self, order_data: Dict[str, Any]) -> Dict[str, Any]:
errors = []
if not order_data.get('customer_email'):
errors.append('Customer email is required')
items = order_data.get('items', [])
if not items:
errors.append('Order must contain at least one item')
for index, item in enumerate(items):
if not item.get('sku'):
errors.append(f'Item {index}: SKU is required')
if not item.get('quantity') or item.get('quantity') <= 0:
errors.append(f'Item {index}: Quantity must be positive')
return {
'is_valid': len(errors) == 0,
'errors': errors
}
# tests/test_order_processor.py
import pytest
from unittest.mock import AsyncMock, Mock
from src.order_processor import OrderProcessor
class TestOrderProcessor:
@pytest.fixture
def mock_client(self):
client = Mock()
client.orders = Mock()
client.orders.create = AsyncMock()
return client
@pytest.fixture
def mock_logger(self):
return Mock()
@pytest.fixture
def order_processor(self, mock_client, mock_logger):
return OrderProcessor(mock_client, mock_logger)
def test_validate_order_valid_data(self, order_processor):
order_data = {
'customer_email': '[email protected]',
'items': [{'sku': 'ITEM-001', 'quantity': 2}]
}
result = order_processor.validate_order(order_data)
assert result['is_valid'] is True
assert len(result['errors']) == 0
def test_validate_order_missing_email(self, order_processor):
order_data = {
'items': [{'sku': 'ITEM-001', 'quantity': 2}]
}
result = order_processor.validate_order(order_data)
assert result['is_valid'] is False
assert 'Customer email is required' in result['errors']
def test_validate_order_empty_items(self, order_processor):
order_data = {
'customer_email': '[email protected]',
'items': []
}
result = order_processor.validate_order(order_data)
assert result['is_valid'] is False
assert 'Order must contain at least one item' in result['errors']
def test_validate_order_invalid_items(self, order_processor):
order_data = {
'customer_email': '[email protected]',
'items': [
{'sku': 'ITEM-001', 'quantity': 2},
{'quantity': 1}, # Missing SKU
{'sku': 'ITEM-003', 'quantity': -1} # Invalid quantity
]
}
result = order_processor.validate_order(order_data)
assert result['is_valid'] is False
assert 'Item 1: SKU is required' in result['errors']
assert 'Item 2: Quantity must be positive' in result['errors']
@pytest.mark.asyncio
async def test_process_order_success(self, order_processor, mock_client, mock_logger):
order_data = {
'customer_email': '[email protected]',
'items': [{'sku': 'ITEM-001', 'quantity': 2}]
}
expected_order = {'id': 'order_123', **order_data}
mock_client.orders.create.return_value = expected_order
result = await order_processor.process_order(order_data)
mock_client.orders.create.assert_called_once_with(order_data)
mock_logger.info.assert_called_once_with('Order processed successfully: order_123')
assert result == expected_order
@pytest.mark.asyncio
async def test_process_order_validation_error(self, order_processor, mock_client):
order_data = {'items': []} # Invalid data
with pytest.raises(ValueError, match='Validation failed'):
await order_processor.process_order(order_data)
mock_client.orders.create.assert_not_called()
@pytest.mark.asyncio
async def test_process_order_api_error(self, order_processor, mock_client, mock_logger):
order_data = {
'customer_email': '[email protected]',
'items': [{'sku': 'ITEM-001', 'quantity': 2}]
}
mock_client.orders.create.side_effect = Exception('API Error')
with pytest.raises(Exception, match='API Error'):
await order_processor.process_order(order_data)
mock_logger.error.assert_called_once_with('Order processing failed: API Error')
Integration Testing
Testing API Interactions
- Node.js
- Python
Copy
// __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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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: '[email protected]',
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)`);
}
});
});
Copy
# tests/integration/test_orders_integration.py
import pytest
import asyncio
from stateset import StateSetClient
from src.order_processor import OrderProcessor
@pytest.mark.integration
class TestOrdersIntegration:
@pytest.fixture(scope="class")
async def client(self):
return StateSetClient(
api_key=os.getenv('STATESET_TEST_API_KEY'),
environment='sandbox'
)
@pytest.fixture(scope="class")
async def order_processor(self, client):
return OrderProcessor(client)
@pytest.fixture
def order_data(self):
return {
'customer_email': '[email protected]',
'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'
}
}
@pytest.mark.asyncio
async def test_create_and_retrieve_order(self, order_processor, client, order_data):
# Create order
created_order = await order_processor.process_order(order_data)
assert created_order['id'] is not None
assert created_order['customer_email'] == order_data['customer_email']
assert created_order['status'] == 'pending'
# Retrieve order
retrieved_order = await client.orders.get(created_order['id'])
assert retrieved_order['id'] == created_order['id']
assert retrieved_order['customer_email'] == order_data['customer_email']
# Cleanup
await client.orders.delete(created_order['id'])
@pytest.mark.asyncio
async def test_order_status_updates(self, order_processor, client, order_data):
order_data['customer_email'] = '[email protected]'
order = await order_processor.process_order(order_data)
# Update order status
updated_order = await client.orders.update(order['id'], {
'status': 'confirmed'
})
assert updated_order['status'] == 'confirmed'
# Verify status change
retrieved_order = await client.orders.get(order['id'])
assert retrieved_order['status'] == 'confirmed'
# Cleanup
await client.orders.delete(order['id'])
@pytest.mark.asyncio
async def test_rate_limiting_handling(self, client):
# Make multiple concurrent requests
tasks = [client.orders.list(limit=1) for _ in range(10)]
results = await asyncio.gather(*tasks, return_exceptions=True)
# At least some should succeed
successful = [r for r in results if not isinstance(r, Exception)]
assert len(successful) > 0
# Check for rate limiting
rate_limited = [r for r in results if isinstance(r, Exception) and getattr(r, 'status', None) == 429]
if rate_limited:
print(f"{len(rate_limited)} requests were rate limited (expected)")
End-to-End Testing
Complete Workflow Testing
- Playwright (Node.js)
- Cypress
Copy
// 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: '[email protected]',
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('[email protected]');
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('[email protected]');
});
test('should handle order cancellation workflow', async ({ page }) => {
// Create order
const orderData = {
customer_email: '[email protected]',
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');
});
});
Copy
// cypress/e2e/order-management.cy.js
describe('Order Management E2E', () => {
let testOrderId;
beforeEach(() => {
// Login to dashboard
cy.login();
});
afterEach(() => {
// Cleanup test orders
if (testOrderId) {
cy.cleanupOrder(testOrderId);
}
});
it('should create and manage order through complete lifecycle', () => {
// Create order via API
cy.createTestOrder().then((order) => {
testOrderId = order.id;
// Visit order details page
cy.visit(`/orders/${order.id}`);
// Verify order details
cy.get('[data-cy="order-id"]').should('contain', order.id);
cy.get('[data-cy="order-status"]').should('contain', 'pending');
cy.get('[data-cy="customer-email"]').should('contain', order.customer_email);
// Confirm order
cy.get('[data-cy="confirm-order"]').click();
cy.get('[data-cy="order-status"]').should('contain', 'confirmed');
// Add shipping information
cy.get('[data-cy="add-shipping"]').click();
cy.get('[data-cy="tracking-number"]').type('CY-TEST-123');
cy.get('[data-cy="carrier"]').select('UPS');
cy.get('[data-cy="submit-shipping"]').click();
// Verify shipping status
cy.get('[data-cy="order-status"]').should('contain', 'shipped');
cy.get('[data-cy="tracking-number"]').should('contain', 'CY-TEST-123');
// Verify API state
cy.verifyOrderStatus(order.id, 'shipped');
});
});
it('should handle order search and filtering', () => {
// Create multiple test orders
cy.createMultipleTestOrders(5).then((orders) => {
orders.forEach(order => testOrderId = order.id); // Keep last for cleanup
cy.visit('/orders');
// Test search functionality
cy.get('[data-cy="order-search"]').type(orders[0].customer_email);
cy.get('[data-cy="search-results"]').should('contain', orders[0].id);
// Test status filtering
cy.get('[data-cy="status-filter"]').select('pending');
cy.get('[data-cy="order-list"] tr').should('have.length.at.least', 1);
// Test date range filtering
cy.get('[data-cy="date-from"]').type('2023-01-01');
cy.get('[data-cy="date-to"]').type('2023-12-31');
cy.get('[data-cy="apply-filters"]').click();
cy.get('[data-cy="order-list"]').should('be.visible');
});
});
});
// cypress/support/commands.js
Cypress.Commands.add('login', () => {
cy.session('login', () => {
cy.visit('/login');
cy.get('[data-cy="email"]').type(Cypress.env('TEST_USER_EMAIL'));
cy.get('[data-cy="password"]').type(Cypress.env('TEST_USER_PASSWORD'));
cy.get('[data-cy="login-button"]').click();
cy.url().should('include', '/dashboard');
});
});
Cypress.Commands.add('createTestOrder', () => {
return cy.request({
method: 'POST',
url: `${Cypress.env('API_BASE_URL')}/orders`,
headers: {
'Authorization': `Bearer ${Cypress.env('STATESET_API_KEY')}`,
'Content-Type': 'application/json'
},
body: {
customer_email: '[email protected]',
items: [
{
sku: 'CY-TEST-ITEM',
quantity: 1,
price: 2999
}
],
shipping_address: {
line1: '123 Cypress Street',
city: 'Test City',
state: 'CA',
postal_code: '90210',
country: 'US'
}
}
}).then((response) => response.body);
});
Cypress.Commands.add('verifyOrderStatus', (orderId, expectedStatus) => {
cy.request({
method: 'GET',
url: `${Cypress.env('API_BASE_URL')}/orders/${orderId}`,
headers: {
'Authorization': `Bearer ${Cypress.env('STATESET_API_KEY')}`
}
}).then((response) => {
expect(response.body.status).to.equal(expectedStatus);
});
});
Cypress.Commands.add('cleanupOrder', (orderId) => {
cy.request({
method: 'DELETE',
url: `${Cypress.env('API_BASE_URL')}/orders/${orderId}`,
headers: {
'Authorization': `Bearer ${Cypress.env('STATESET_API_KEY')}`
},
failOnStatusCode: false
});
});
Performance Testing
Load Testing with Artillery
Copy
# 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 Testing
Copy
// 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);
});
Testing Best Practices
Test Organization
- Separate unit, integration, and E2E tests
- Use descriptive test names
- Group related tests together
- Follow AAA pattern (Arrange, Act, Assert)
Test Data Management
- Use factories for test data generation
- Clean up test data after tests
- Use isolated test environments
- Mock external dependencies
CI/CD Integration
- Run tests on every commit
- Separate fast and slow test suites
- Use parallel test execution
- Generate coverage reports
Monitoring & Alerting
- Monitor test execution times
- Alert on test failures
- Track test coverage trends
- Performance regression detection
Continuous Integration Setup
Copy
# .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 }}
Next Steps
After implementing comprehensive testing:- Monitor Test Metrics: Track coverage, execution time, and flakiness
- Expand Test Scenarios: Add edge cases and error conditions
- Performance Baselines: Establish performance benchmarks
- Test Automation: Integrate with deployment pipelines