Testing & TDD

Testing Microservices

18 min Lesson 27 of 35

Testing Microservices

Testing microservices architecture presents unique challenges. Services must communicate reliably, contracts must be maintained, and distributed complexity managed. This lesson explores testing strategies for microservices.

Microservices Testing Pyramid

Testing Levels:
  • Unit Tests: Test individual service components
  • Integration Tests: Test service interactions
  • Contract Tests: Verify API contracts
  • End-to-End Tests: Test complete user journeys

Contract Testing with Pact

// Consumer test - Order Service class UserServiceContractTest extends TestCase { public function test_get_user_contract() { $request = new ConsumerRequest(); $request->setMethod('GET') ->setPath('/api/users/123'); $response = new ProviderResponse(); $response->setStatus(200) ->setBody([ 'id' => 123, 'name' => 'John Doe', 'email' => 'john@example.com' ]); $this->builder ->given('User 123 exists') ->uponReceiving('Request for user 123') ->with($request) ->willRespondWith($response); $user = $this->userService->getUser(123); $this->assertEquals('John Doe', $user['name']); } }

Service Virtualization

// Mock external service responses class PaymentServiceMock { public static function mockSuccessfulPayment() { Http::fake([ 'payment-service.com/api/charge' => Http::response([ 'transaction_id' => 'txn_123', 'status' => 'success', 'amount' => 100.00 ], 200) ]); } public static function mockFailedPayment() { Http::fake([ 'payment-service.com/api/charge' => Http::response([ 'error' => 'Insufficient funds' ], 402) ]); } }

Testing Service Communication

// Test HTTP communication between services public function test_order_service_calls_user_service() { Http::fake([ 'user-service.com/api/users/*' => Http::response([ 'id' => 1, 'name' => 'Test User' ]) ]); $order = Order::factory()->create(); $result = $this->orderService->processOrder($order); Http::assertSent(function ($request) { return $request->url() == 'http://user-service.com/api/users/1'; }); }

Testing Message Queues

// Test event publishing public function test_order_created_event_is_published() { Event::fake([OrderCreated::class]); $order = $this->orderService->createOrder([ 'user_id' => 1, 'total' => 100 ]); Event::assertDispatched(OrderCreated::class, function ($event) use ($order) { return $event->order->id === $order->id; }); }

Testing Circuit Breakers

// Test circuit breaker pattern public function test_circuit_breaker_opens_on_failures() { Http::fake([ 'external-service.com/*' => Http::response([], 500) ]); // Trigger multiple failures for ($i = 0; $i < 5; $i++) { try { $this->service->callExternalApi(); } catch (Exception $e) { // Expected failures } } // Circuit should be open $this->assertTrue($this->service->isCircuitOpen()); // Next call should fail fast $this->expectException(CircuitBreakerOpenException::class); $this->service->callExternalApi(); }

Testing Service Discovery

// Test service registry public function test_service_discovery() { $registry = new ServiceRegistry(); $registry->register('user-service', 'http://localhost:8001'); $registry->register('order-service', 'http://localhost:8002'); $userServiceUrl = $registry->discover('user-service'); $this->assertEquals('http://localhost:8001', $userServiceUrl); }
Microservices Testing Tips:
  • Use contract testing to prevent integration issues
  • Mock external services in unit tests
  • Test failure scenarios and timeouts
  • Verify retry mechanisms
  • Test service degradation gracefully
Common Pitfalls:
  • Not testing network failures
  • Ignoring eventual consistency
  • Over-reliance on end-to-end tests
  • Not versioning API contracts
  • Missing timeout configurations
Exercise: Create a contract test between two services. Test successful communication, error handling, and timeout scenarios.