Testing microservices is fundamentally different from testing a monolith. You have distributed state, network boundaries, independent deployments, and inter-service contracts that can drift apart silently. A solid microservices testing strategy with JUnit 6 operates at three distinct levels: integration tests within a single service, contract tests between services, and end-to-end tests across the entire system. This guide covers all three with concrete, production-grade examples.
The Microservices Testing Pyramid

Level 1: Integration Tests Within a Single Service
Integration tests for a microservice verify that all layers of the service work together correctly — controller, service, repository, and database — but in isolation from other services. External service calls are stubbed using WireMock.
<!-- WireMock: HTTP stub server for testing external service calls -->
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-jetty12</artifactId>
<version>3.9.1</version>
<scope>test</scope>
</dependency>
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@DisplayName("Order Service — integration test with stubbed inventory service")
class OrderServiceIntegrationTest {
// Testcontainers: real PostgreSQL for the order service’s own database
@Container
static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
// WireMock: stub server simulating the external Inventory Service
@RegisterExtension
static WireMockExtension inventoryServiceStub = WireMockExtension.newInstance()
.options(wireMockConfig().dynamicPort())
.build();
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
// Point the order service’s datasource to the Testcontainers DB
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
// Point the order service’s inventory client to the WireMock stub
registry.add("inventory.service.url",
() -> inventoryServiceStub.baseUrl());
}
@Autowired
private TestRestTemplate restTemplate;
@Test
@DisplayName("Creating order succeeds when inventory has sufficient stock")
void creatingOrderSucceedsWhenStockAvailable() {
// Stub: inventory service confirms stock is available
inventoryServiceStub.stubFor(
WireMock.get(WireMock.urlPathEqualTo("/api/inventory/LAPTOP-01"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{"productId":"LAPTOP-01","available":true,"quantity":50}")
.withStatus(200))
);
// Act: call the order service
CreateOrderRequest request =
new CreateOrderRequest("[email protected]", "LAPTOP-01", 1);
ResponseEntity<OrderDto> response =
restTemplate.postForEntity("/api/orders", request, OrderDto.class);
// Assert
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals(OrderStatus.CONFIRMED, response.getBody().getStatus());
// Verify the order service actually called the inventory service
inventoryServiceStub.verify(1,
WireMock.getRequestedFor(
WireMock.urlPathEqualTo("/api/inventory/LAPTOP-01")));
}
@Test
@DisplayName("Creating order fails when inventory has no stock")
void creatingOrderFailsWhenOutOfStock() {
inventoryServiceStub.stubFor(
WireMock.get(WireMock.urlPathEqualTo("/api/inventory/SOLD-OUT-01"))
.willReturn(WireMock.aResponse()
.withBody("{"productId":"SOLD-OUT-01","available":false,"quantity":0}")
.withStatus(200))
);
CreateOrderRequest request =
new CreateOrderRequest("[email protected]", "SOLD-OUT-01", 1);
ResponseEntity<ErrorDto> response =
restTemplate.postForEntity("/api/orders", request, ErrorDto.class);
assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
assertTrue(response.getBody().getMessage().contains("out of stock"));
}
}
Continue reading Testing Microservices with JUnit 6: Integration, Contract & E2E