A testing strategy for Boot apps, @SpringBootTest and @MockBean/@MockitoBean, DevTools, mocking external services and whole microservices (WireMock, Testcontainers, contract tests), Docker images done well, JAR vs WAR, CI/CD integration, the Whitelabel page, handling 404s, and deploying to AWS or Azure.
Published September 25, 2026
This lesson covers the path from "it works on my machine" to "it runs in production". Interviewers want a testing pyramid you've actually used, a Docker image that isn't 800 MB running as root, and error handling that returns proper JSON instead of the Whitelabel page.
Short answer: Use a test pyramid:
@WebMvcTest for controllers (with MockMvc);@DataJpaTest for repositories and queries;@JsonTest for serialisation;@RestClientTest for HTTP clients.@SpringBootTest with Testcontainers (a real PostgreSQL or Kafka), covering critical flows end to end.Key points to cover:
@MockitoBean combinations in each class.Learn it in depth → JUnit 5
@SpringBootTest and @MockBean used?Short answer: @SpringBootTest boots the full application context. Add webEnvironment = RANDOM_PORT to start the real server, for HTTP tests with TestRestTemplate or WebTestClient. @MockBean (replaced by @MockitoBean in Boot 3.4 / Spring Framework 6.2) replaces a bean in that context with a Mockito mock. Use it for dependencies you don't want to exercise, such as a payment gateway client.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class CheckoutFlowIT {
@Container @ServiceConnection
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
@MockitoBean PaymentGateway gateway; // replace only the external dependency
@Autowired TestRestTemplate http;
@Test void placesOrder() {
when(gateway.charge(any())).thenReturn(PaymentResult.approved("pay_123"));
var response = http.postForEntity("/api/orders", sampleOrder(), OrderResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
}
}
Short answer: DevTools speeds up local development:
Key points to cover:
optional/developmentOnly, so it isn't shipped at all.Short answer: Choose the level to mock at:
@MockitoBean replaces your client bean (for example, PaymentGateway). It's fast, but it skips the HTTP and serialisation code.MockRestServiceServer for RestTemplate/RestClient) runs a fake HTTP server. Your real client code, timeouts, headers and JSON mapping are all exercised.@WireMockTest(httpPort = 8089)
class PaymentClientTest {
@Test void mapsDeclines() {
stubFor(post("/charges").willReturn(aResponse().withStatus(402)
.withHeader("Content-Type", "application/json").withBody("{\"code\":\"card_declined\"}")));
assertThrows(PaymentDeclinedException.class, () -> client.charge(sampleCharge()));
}
}
Short answer:
Common trap: relying only on hand-written mocks. The consumer's tests pass while the real provider has changed its API. Contract tests close that gap.
Short answer: You have three options:
./mvnw spring-boot:build-image. No Dockerfile is needed, and you get layered, reproducible, non-root images with memory-calculator tuning.FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests && java -Djarmode=tools -jar target/app.jar extract --layers --launcher --destination extracted
FROM eclipse-temurin:21-jre
RUN useradd --system app
USER app
WORKDIR /app
COPY --from=build /app/extracted/dependencies/ ./
COPY --from=build /app/extracted/spring-boot-loader/ ./
COPY --from=build /app/extracted/snapshot-dependencies/ ./
COPY --from=build /app/extracted/application/ ./
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "org.springframework.boot.loader.launch.JarLauncher"]
Key points to cover:
MaxRAMPercentage). Add health checks, and scan the image for vulnerabilities.Learn it in depth → Multi-Stage Builds
Short answer:
./mvnw package creates an executable fat JAR with an embedded server. Run it with java -jar app.jar, or containerise it.<packaging>war</packaging>, mark the embedded Tomcat starter as provided, and make the main class extend SpringBootServletInitializer (overriding configure). Then deploy the WAR to an external Tomcat, JBoss or WebLogic.@SpringBootApplication
public class ShopApplication extends SpringBootServletInitializer {
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder b) { return b.sources(ShopApplication.class); }
public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); }
}
Key points to cover:
java -jar. Choose WAR only when the organisation mandates shared application servers.Short answer: A typical pipeline (GitHub Actions, GitLab CI or Jenkins):
./mvnw verify), with caching of ~/.m2.Key points to cover:
Learn it in depth → CI/CD Pipeline Design
Short answer: It's Spring Boot's default HTML error view, rendered by BasicErrorController at /error when an error isn't handled and no custom error page exists. Typical triggers are an unmapped URL (404) or an unhandled exception (500).
The fixes:
@RestControllerAdvice, and return Problem Details JSON (spring.mvc.problemdetails.enabled=true).templates/error/404.html, error/5xx.html or error.html.server.error.whitelabel.enabled=false, if a proxy serves the error pages.Key points to cover:
server.error.include-stacktrace=never, the default in production).Short answer:
OrderNotFoundException), or ResponseStatusException(NOT_FOUND), and map it in @RestControllerAdvice.NoResourceFoundException. ResponseEntityExceptionHandler turns it into a 404 Problem Detail, or you can add your own @ExceptionHandler(NoResourceFoundException.class).error/404.html template. Or, as a last resort, implement a custom ErrorController for full control.@RestControllerAdvice
class NotFoundAdvice {
@ExceptionHandler({ OrderNotFoundException.class, NoResourceFoundException.class })
ProblemDetail notFound(Exception ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "The requested resource does not exist");
}
}
Learn it in depth → Spring Exception Handling
Short answer:
/actuator/health/readiness.Key points to cover:
Learn it in depth → AWS EC2
Q: Why are my @SpringBootTest tests slow?
A: Usually because the context is rebuilt too often (a different @MockitoBean set, @DirtiesContext, or different properties per class), or because the full context starts where a slice test would do. Standardise the test configurations, and prefer slices.
Q: What does @ServiceConnection do?
A: With Testcontainers (Boot 3.1+), it automatically wires the container's connection details (URL, username, password) into the application's configuration. You don't need @DynamicPropertySource boilerplate.
Q: How do you make container images smaller and faster to start?
A: Use JRE or distroless base images, layered JARs, and jlink custom runtimes. Use Class Data Sharing and Spring AOT, or GraalVM native images, when startup time is critical (serverless).
Q: How should a deployment wait for the app to be ready?
A: Use readiness probes on /actuator/health/readiness, with group-specific checks, an appropriate startup probe for slow starters, and server.shutdown=graceful, so rolling updates drain traffic cleanly.