-
Notifications
You must be signed in to change notification settings - Fork 0
Integration Testing
Integration testing is the phase in software testing where individual software modules are combined and tested as a group. The purpose is to verify that different components of the application work together correctly, exposing defects in the interfaces and interactions between integrated components.
- Verify that components work together as expected
- Test the communication between different modules
- Identify issues in the interaction between integrated components
- Ensure that interfaces between components are functioning correctly
- Validate the workflow across multiple components
All components are integrated simultaneously and tested as a complete system.
Advantages:
- Simple approach for small systems
- No need for stubs or drivers
Disadvantages:
- Difficult to localize faults
- Integration issues found late in the process
- Not suitable for large systems
Components are integrated and tested one at a time.
Start with top-level modules and gradually add lower-level modules.
Advantages:
- Early testing of high-level logic
- Requires only stubs for lower-level components
- Major design issues detected early
Disadvantages:
- Basic functionality tested late
- Requires many stubs
- Lower-level modules not tested independently
Start with lower-level modules and gradually move up.
Advantages:
- Basic functionality tested early
- No stubs required, only test drivers
- Good when lower-level modules are critical or risky
Disadvantages:
- High-level logic tested late
- Requires test drivers
- UI tested late in the process
Combine top-down and bottom-up approaches.
Advantages:
- Leverages advantages of both approaches
- Suitable for large, complex systems
- Tests critical middle layers early
Disadvantages:
- More complex to plan and execute
- Requires both stubs and drivers
- May need additional resources
Tests APIs and the interactions between different services. Focuses on:
- Request/response validation
- Error handling
- Authentication and authorization
- API performance and scalability
Tests the interaction between an application and its database. Focuses on:
- CRUD operations
- Transaction management
- Data integrity constraints
- Migration scripts
Tests the interaction between microservices or service layers. Focuses on:
- Service contracts
- Message formats
- Fault tolerance
- Asynchronous communication
Tests the integration with external services and libraries. Focuses on:
- Authentication with external systems
- Data mapping between systems
- Error handling for external service failures
- API versioning and compatibility
Create a dedicated test database environment:
// Example of setting up a test database in Jest
beforeAll(async () => {
// Connect to test database
testDb = await connectToTestDatabase();
// Seed with test data
await seedTestData(testDb);
});
afterAll(async () => {
// Clean up test database
await cleanupTestDatabase(testDb);
await disconnectFromTestDatabase(testDb);
});Use database transactions to ensure test isolation:
// Example using a transaction in a test
beforeEach(async () => {
// Start a transaction
transaction = await db.beginTransaction();
});
afterEach(async () => {
// Roll back the transaction after each test
await transaction.rollback();
});
test('should create a user and associated profile', async () => {
// Test code that creates a user and profile
await userService.createUserWithProfile(userData, profileData);
// Verify user and profile were created
const user = await transaction.findUserByEmail(userData.email);
const profile = await transaction.findProfileByUserId(user.id);
expect(user).not.toBeNull();
expect(profile).not.toBeNull();
expect(profile.userId).toBe(user.id);
});Use mocks for external services that are not the focus of the integration test:
// Example mocking a payment service while testing order processing
test('should process order and apply payment', async () => {
// Mock the payment service
const mockPaymentService = {
processPayment: jest.fn().mockResolvedValue({
success: true,
transactionId: 'mock-transaction-123'
})
};
// Inject the mock into the order service
const orderService = new OrderService({
paymentService: mockPaymentService,
// Real database connection for integration testing
orderRepository: orderRepository
});
// Test order processing
const result = await orderService.createOrder(orderData);
// Verify order was saved to the database
const savedOrder = await orderRepository.findById(result.orderId);
expect(savedOrder).not.toBeNull();
expect(savedOrder.status).toBe('paid');
// Verify payment service was called correctly
expect(mockPaymentService.processPayment).toHaveBeenCalledWith({
amount: orderData.totalAmount,
paymentMethod: orderData.paymentMethod
});
});Create test environments that closely match production:
- Use Docker containers to simulate production services
- Set up test instances of databases and message queues
- Configure test services with realistic settings
Cover both standard and exceptional flows:
// Happy path test
test('should successfully transfer money between accounts', async () => {
// Test successful money transfer
});
// Edge case tests
test('should handle insufficient funds during transfer', async () => {
// Test transfer with insufficient balance
});
test('should handle transfer to closed account', async () => {
// Test transfer to an account that is closed
});
test('should handle concurrent transfers to the same account', async () => {
// Test race conditions with multiple simultaneous transfers
});Implement contract tests to verify service interfaces:
// Consumer-driven contract test example
test('user service returns data in the expected format', async () => {
// Call the user service
const response = await userService.getUserProfile(userId);
// Verify the contract is fulfilled
expect(response).toMatchSchema({
type: 'object',
required: ['id', 'name', 'email', 'createdAt'],
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string', format: 'email' },
createdAt: { type: 'string', format: 'date-time' }
}
});
});Create helper functions for test data setup:
// Example test data setup function
async function setupOrderWithProducts() {
const customer = await createTestCustomer();
const products = await createTestProducts(3);
const order = await createTestOrder(customer.id, products);
return { customer, products, order };
}
test('should calculate order totals correctly', async () => {
// Set up test data
const { order, products } = await setupOrderWithProducts();
// Test calculation logic
await orderService.calculateTotals(order.id);
// Verify results
const updatedOrder = await orderRepository.findById(order.id);
const expectedTotal = products.reduce((sum, p) => sum + p.price, 0);
expect(updatedOrder.subtotal).toBeCloseTo(expectedTotal);
expect(updatedOrder.tax).toBeCloseTo(expectedTotal * 0.1); // Assuming 10% tax
expect(updatedOrder.total).toBeCloseTo(expectedTotal * 1.1);
});- Postman/Newman: HTTP API testing with collection runners
- REST Assured: Java library for REST API testing
- Supertest: Node.js HTTP assertion library
- Pact: Contract testing for APIs
- Testcontainers: Provides lightweight, throwaway instances of databases
- Flyway/Liquibase: Database migration tools useful for test setup
- DBUnit: Database testing framework for Java
- pg-mem: In-memory PostgreSQL for Node.js testing
- WireMock: Mock HTTP services for testing
- MockServer: Easy mocking of HTTP dependencies
- Hoverfly: Lightweight service virtualization
- Docker Compose: For setting up multi-service test environments
test('complete user registration flow', async () => {
// Test user data
const userData = {
email: 'test@example.com',
password: 'securePassword123',
name: 'Test User'
};
// 1. Register the user
const registrationResult = await userService.register(userData);
expect(registrationResult.success).toBe(true);
// 2. Verify user is saved in the database
const savedUser = await userRepository.findByEmail(userData.email);
expect(savedUser).not.toBeNull();
expect(savedUser.name).toBe(userData.name);
expect(savedUser.isActive).toBe(false); // Should be inactive until email verification
// 3. Verify verification email was sent
expect(emailService.sendVerification).toHaveBeenCalledWith(
userData.email,
expect.any(String) // Verification token
);
// 4. Verify user account with the token
const verificationToken = emailService.sendVerification.mock.calls[0][1];
const verificationResult = await userService.verifyEmail(verificationToken);
expect(verificationResult.success).toBe(true);
// 5. Verify user is now active
const activeUser = await userRepository.findByEmail(userData.email);
expect(activeUser.isActive).toBe(true);
// 6. Verify user can log in
const loginResult = await authService.login(userData.email, userData.password);
expect(loginResult.success).toBe(true);
expect(loginResult.token).toBeDefined();
});test('end-to-end order processing flow', async () => {
// 1. Create test customer and products
const customer = await createTestCustomer();
const products = await createTestProducts(2);
// 2. Create a shopping cart
const cart = await cartService.createCart(customer.id);
// 3. Add products to cart
await cartService.addItem(cart.id, products[0].id, 2); // 2 quantity
await cartService.addItem(cart.id, products[1].id, 1); // 1 quantity
// 4. Apply a coupon
const coupon = await createTestCoupon('TESTCODE', 10); // 10% discount
await cartService.applyCoupon(cart.id, coupon.code);
// 5. Create order from cart
const order = await orderService.createFromCart(cart.id);
// 6. Verify order details
expect(order.customerId).toBe(customer.id);
expect(order.items.length).toBe(2);
expect(order.items[0].productId).toBe(products[0].id);
expect(order.items[0].quantity).toBe(2);
expect(order.discountPercentage).toBe(10);
// 7. Process payment
const paymentResult = await paymentService.processOrderPayment(order.id, {
type: 'credit_card',
cardNumber: '4111111111111111', // Test card number
expiryMonth: '12',
expiryYear: '2030',
cvv: '123'
});
expect(paymentResult.success).toBe(true);
// 8. Verify order status is updated
const updatedOrder = await orderRepository.findById(order.id);
expect(updatedOrder.status).toBe('paid');
expect(updatedOrder.paymentId).toBe(paymentResult.transactionId);
// 9. Check inventory was updated
const updatedProduct1 = await productRepository.findById(products[0].id);
const updatedProduct2 = await productRepository.findById(products[1].id);
expect(updatedProduct1.stockQuantity).toBe(products[0].stockQuantity - 2);
expect(updatedProduct2.stockQuantity).toBe(products[1].stockQuantity - 1);
});Our repository includes several integration testing examples: