By WebNest Studio Engineering Team
Spring Framework Tutorial
Testing Spring Applications
Spring applications benefit from tests at several levels: fast, isolated unit tests that don't touch the framework at all, and integration tests that load some or all of a real Spring ApplicationContext to verify beans wire together and behave correctly end-to-end. The Spring ecosystem — most visibly through Spring Boot's testing support — provides dedicated annotations for each of these levels so you aren't forced to choose only one style.
@SpringBootTest is the most common integration-testing entry point in Spring Boot applications: it boots a real (or near-real) ApplicationContext for the test, letting you autowire actual beans and exercise real wiring. @MockBean complements this by letting you replace a specific bean in that context with a Mockito mock, which is useful for isolating the class under test from a slow or external collaborator (like a payment gateway) while still testing everything else through a genuine Spring context.
@SpringBootTest — Full or Sliced Integration Tests
Annotating a test class with @SpringBootTest tells Spring Boot's test support to search for a @SpringBootApplication-annotated class and use it to build a full ApplicationContext for the test, exactly as it would for the running application. This is the most realistic form of test, exercising real bean wiring, configuration, and (optionally, via webEnvironment) an actual embedded web server. Because it boots the entire context, it is also the slowest style of test, so it's typically reserved for a smaller number of true end-to-end checks rather than every test case.
@MockBean — Replacing a Bean with a Mock
@MockBean, applied to a field inside a test class, tells Spring's test context to replace whatever real bean of that type exists in the ApplicationContext with a Mockito mock for the duration of that test. This lets you test a service class through a genuine Spring context while stubbing out a specific dependency (an external API client, an email sender) so the test doesn't depend on that dependency's real, possibly slow or unreliable, behavior.
Working Directly with ApplicationContext in Tests
For lower-level verification — confirming that a particular bean exists, is of the expected type, or was wired correctly — tests can inject the ApplicationContext itself and call getBean() directly. This is useful for configuration-focused tests (e.g. verifying a @Profile-guarded bean is or isn't present under a given profile) without needing the full weight of testing actual business logic through the beans.
@SpringBootTest— loads a full ApplicationContext for realistic integration testing (part of the broader Spring ecosystem's testing support, most commonly used in Spring Boot projects).@MockBean— replaces a real bean in the test context with a Mockito mock.- Plain JUnit + Mockito (no Spring annotations at all) remains the right choice for pure unit tests of a class with manually constructed dependencies.
@DataJpaTest,@WebMvcTest, and other "slice" annotations load only the portion of the context relevant to one layer, for faster, more focused tests.
Example
An integration test using @SpringBootTest and @MockBean to isolate an external dependency
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
interface PaymentGateway {
boolean charge(double amount);
}
class CheckoutService {
private final PaymentGateway paymentGateway;
CheckoutService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
String checkout(double amount) {
return paymentGateway.charge(amount) ? "Payment successful" : "Payment declined";
}
}
@SpringBootTest
class CheckoutServiceTest {
@Autowired
private CheckoutService checkoutService;
@MockBean
private PaymentGateway paymentGateway; // replaces the real gateway bean with a mock
@Test
void checkoutSucceedsWhenGatewayApproves() {
when(paymentGateway.charge(99.99)).thenReturn(true);
String result = checkoutService.checkout(99.99);
assertEquals("Payment successful", result);
}
}
Test passes: checkoutService.checkout(99.99) returns "Payment successful" because the mocked PaymentGateway was stubbed to return true, with no real payment network call made.
Common Mistakes
- Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate.
- Forgetting that @MockBean replaces the bean for the whole test context, which can hide real integration issues that would only surface with the actual dependency wired in.
- Mixing manual Mockito.mock() creation with @MockBean inconsistently, leading to confusion about which mock instance is actually injected into the Spring context.
- Not resetting or re-stubbing a @MockBean between test methods when its behavior needs to differ per test, causing stale stubbed behavior to leak across tests.
Exercise
A teammate is about to make this mistake: Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate. Use @SpringBootTest — Full or Sliced Integration Tests to review the decision. Write a corrected implementation or design sketch that follows @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution., then name one edge case you would check.
Show hints
- Start with @SpringBootTest — Full or Sliced Integration Tests and compare your approach with An integration test using @SpringBootTest and @MockBean to isolate an external dependency. Identify the decision or behavior the lesson recommends.
- Walk through your scenario step by step. Confirm that it follows @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution. and that it would catch the failure described by Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate..
Quick quiz
1. Which choice best describes the recommended approach in Testing Spring Applications?
- A. Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate.
- B. Forgetting that @MockBean replaces the bean for the whole test context, which can hide real integration issues that would only surface with the actual dependency wired in.
- C. @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution.
Show answer
C. This is the recommended approach: @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution. Avoid: Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate.
Interview questions
Why does this principle matter in Testing Spring Applications: @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution.
Show answer
@SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution. Apply this in @SpringBootTest — Full or Sliced Integration Tests; the example An integration test using @SpringBootTest and @MockBean to isolate an external dependency shows one way to do it. Avoid Using @SpringBootTest for every single test class, resulting in a slow test suite that boots a full context repeatedly instead of using faster slice tests (@WebMvcTest, @DataJpaTest) or plain unit tests where appropriate. and explain how your implementation or design prevents that failure.
Key Points to Remember
- @SpringBootTest loads a real (or near-real) ApplicationContext for realistic integration testing, at the cost of slower test execution.
- @MockBean swaps a specific bean in the test context for a Mockito mock, isolating the class under test from a chosen dependency.
- Slice test annotations (@WebMvcTest, @DataJpaTest) load only the relevant portion of the context for faster, layer-focused tests.
- Plain unit tests without any Spring annotations remain appropriate when a class's dependencies can be constructed and mocked manually.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering