Contract Testing in Java Microservices (2025)

Contract testing is crucial for ensuring compatibility between microservices in a distributed system. This comprehensive guide explores contract testing tools and techniques in Java microservices.
Pro Tip: Contract testing helps catch integration issues early in the development cycle, reducing deployment risks.
Table of Contents
Pact Testing
Note: Pact is a consumer-driven contract testing tool that helps ensure compatibility between service consumers and providers.
Pact Configuration
au.com.dius.pact.consumer
junit5
4.3.2
test
au.com.dius.pact.provider
spring
4.3.2
test
Consumer Pact Test
@PactTestFor(providerName = "order-service", hostInterface = "localhost")
public class OrderClientPactTest {
private OrderClient orderClient;
@BeforeEach
void setUp() {
orderClient = new OrderClient("http://localhost:8080");
}
@Pact(consumer = "order-client")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("Order exists")
.uponReceiving("A request for an order")
.path("/api/orders/123")
.method("GET")
.willRespondWith()
.status(200)
.headers(Map.of("Content-Type", "application/json"))
.body(new PactDslJsonBody()
.numberType("id", 123)
.stringType("status", "COMPLETED")
.numberType("amount", 100.0))
.toPact();
}
@Test
@PactTestFor(pactMethod = "createPact")
void testGetOrder(MockServer mockServer) {
orderClient.setBaseUrl(mockServer.getUrl());
Order order = orderClient.getOrder(123);
assertEquals(123, order.getId());
assertEquals("COMPLETED", order.getStatus());
assertEquals(100.0, order.getAmount());
}
}
Spring Cloud Contract
Pro Tip: Spring Cloud Contract provides a DSL for writing contracts and generates tests automatically.
Contract Definition
// src/test/resources/contracts/order-service/order-contract.groovy
package contracts
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return order details"
request {
method GET()
url "/api/orders/123"
}
response {
status OK()
headers {
contentType applicationJson()
}
body([
id: 123,
status: "COMPLETED",
amount: 100.0
])
}
}
Provider Test Generation
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCKMVC)
@AutoConfigureMockMvc
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.LOCAL,
ids = "com.example:order-service:+:stubs:8080"
)
public class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldReturnOrderDetails() throws Exception {
mockMvc.perform(get("/api/orders/123"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(123))
.andExpect(jsonPath("$.status").value("COMPLETED"))
.andExpect(jsonPath("$.amount").value(100.0));
}
}
Consumer-Driven Tests
Note: Consumer-driven contract testing ensures that service providers meet consumer expectations.
Consumer Test Implementation
public class OrderClient {
private final String baseUrl;
private final RestTemplate restTemplate;
public OrderClient(String baseUrl) {
this.baseUrl = baseUrl;
this.restTemplate = new RestTemplate();
}
public Order getOrder(long id) {
String url = baseUrl + "/api/orders/" + id;
ResponseEntity response = restTemplate.getForEntity(url, Order.class);
return response.getBody();
}
}
@SpringBootTest
public class OrderClientIntegrationTest {
@Autowired
private OrderClient orderClient;
@Test
void shouldGetOrderDetails() {
Order order = orderClient.getOrder(123);
assertNotNull(order);
assertEquals(123, order.getId());
assertEquals("COMPLETED", order.getStatus());
assertEquals(100.0, order.getAmount());
}
}
Provider Tests
Pro Tip: Provider tests verify that the service implementation matches the contract.
Provider Implementation
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public ResponseEntity getOrder(@PathVariable long id) {
Order order = orderService.getOrder(id);
return ResponseEntity.ok(order);
}
}
@Service
public class OrderService {
private final OrderRepository orderRepository;
@Autowired
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public Order getOrder(long id) {
return orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
}
Contract Evolution
Note: Managing contract evolution is crucial for maintaining service compatibility.
Contract Versioning
@RestController
@RequestMapping("/api/v1/orders")
public class OrderControllerV1 {
// Version 1 implementation
}
@RestController
@RequestMapping("/api/v2/orders")
public class OrderControllerV2 {
// Version 2 implementation with new fields
}
// Contract for version 1
Contract.make {
description "should return order details v1"
request {
method GET()
url "/api/v1/orders/123"
}
response {
status OK()
body([
id: 123,
status: "COMPLETED",
amount: 100.0
])
}
}
// Contract for version 2
Contract.make {
description "should return order details v2"
request {
method GET()
url "/api/v2/orders/123"
}
response {
status OK()
body([
id: 123,
status: "COMPLETED",
amount: 100.0,
customerId: "CUST123",
orderDate: "2025-03-18T10:00:00Z"
])
}
}
Best Practices
Pro Tip: Following contract testing best practices ensures reliable service integration.
Contract Testing Best Practices
- Use consumer-driven contract testing
- Version your contracts
- Test edge cases and error scenarios
- Keep contracts simple and focused
- Use meaningful test data
- Automate contract testing in CI/CD
- Document contract changes
- Review contract changes with stakeholders
Conclusion
Contract testing is essential for maintaining reliable microservices integration. By implementing these techniques and following best practices, teams can ensure service compatibility and reduce integration issues.