Spring Boot Testing (JUnit, Mockito & Test Slices)
Test Spring boot applications. Master unit test mocks with Mockito, integration tests, and @WebMvcTest slices.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is the difference between unit testing and integration testing in Spring Boot?
Unit testing verifies a single class or method in complete isolation, with all its dependencies replaced by mocks (using Mockito), so the test runs fast and doesn't load any Spring context. Integration testing verifies how multiple components work together — controller, service, repository, and sometimes a real or embedded database — by loading part or all of the Spring application context.
A healthy test suite favors many fast unit tests for business logic and a smaller number of integration tests for critical end-to-end flows, since integration tests are slower (due to context startup) and more brittle to unrelated changes. Spring Boot supports both: plain JUnit + Mockito for unit tests, and annotations like @SpringBootTest for full-context integration tests.
// Unit test — no Spring context
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private PaymentGateway paymentGateway;
@InjectMocks private OrderService orderService;
}
// Integration test — loads full Spring context
@SpringBootTest
class OrderIntegrationTest { ... }
Q2. What does @SpringBootTest do and when should you use it?
@SpringBootTest bootstraps the complete Spring application context for a test, the same way the real application would start, including all auto-configuration, beans, and (optionally) an embedded web server via webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT. This makes it the go-to annotation for true end-to-end integration tests.
Because it loads the entire context, @SpringBootTest is significantly slower than a sliced test like @WebMvcTest or a plain unit test, and Spring caches the context across test classes with identical configuration to reduce repeated startup cost. It should be reserved for tests that genuinely need the full wiring — most tests are better served by a narrower slice annotation or Mockito-based unit tests.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderApplicationTests {
@Autowired private TestRestTemplate restTemplate;
@Test
void createOrder_returns201() {
ResponseEntity<Order> response = restTemplate.postForEntity("/api/orders", request, Order.class);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
}
}
Q3. What is the difference between @Mock, @MockBean, and @InjectMocks?
@Mock (Mockito) creates a mock object for use in plain unit tests that don't load any Spring context; it's paired with @ExtendWith(MockitoExtension.class). @InjectMocks creates an instance of the class under test and automatically injects the fields annotated with @Mock into it via constructor, setter, or field injection.
@MockBean is Spring Boot's test-specific annotation: it creates a Mockito mock and replaces the real bean of that type in the Spring application context, used inside @SpringBootTest or a slice test when you need the context to start but want to stub out one dependency (e.g. an external API client) rather than use the real implementation.
// Pure unit test
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private PaymentGateway paymentGateway;
@InjectMocks private OrderService orderService;
}
// Spring context test with one dependency mocked
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@MockBean private OrderService orderService;
}
Q4. How does @WebMvcTest work, and how is it different from @SpringBootTest?
@WebMvcTest loads only the Spring MVC layer — controllers, @ControllerAdvice, filters, and MVC-related configuration — without starting the full application context, so services, repositories, and the database are not loaded by default and must be provided as @MockBeans.
This makes it much faster than @SpringBootTest and ideal for testing controller behavior in isolation: request mapping, validation, status codes, and JSON serialization, using MockMvc to perform simulated HTTP requests without starting a real server.
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void getUser_returns200() throws Exception {
when(userService.findById(1L)).thenReturn(new User(1L, "John"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"));
}
}
Q5. What is @DataJpaTest and how does it test the repository layer?
@DataJpaTest loads only the JPA-related components — @Entity classes, Spring Data repositories, and the DataSource — without loading services or controllers, giving a focused slice for testing repository queries and entity mappings.
By default it configures an in-memory embedded database (like H2) if available on the classpath and wraps each test in a transaction that's rolled back afterward, keeping tests isolated from each other. It also auto-configures TestEntityManager, a testing-friendly alternative to EntityManager for setting up test data with fine-grained control over flushing.
@DataJpaTest
class UserRepositoryTest {
@Autowired private TestEntityManager entityManager;
@Autowired private UserRepository userRepository;
@Test
void findByEmail_returnsUser() {
entityManager.persist(new User("john@example.com"));
Optional<User> found = userRepository.findByEmail("john@example.com");
assertTrue(found.isPresent());
}
}
Q6. What is the difference between Mockito's when().thenReturn() and @Spy?
when(mock.method()).thenReturn(value) stubs a method on a full mock object, where every method returns a default value (null, 0, false) unless explicitly stubbed — the mock has no real logic behind it at all.
@Spy wraps a real object instance, so unstubbed methods call through to the actual implementation while specific methods can still be selectively stubbed with doReturn().when(). Spies are useful when you want to test real behavior for most of a class but override just one method (e.g. an external call), though they should be used sparingly since partially-real objects can make tests harder to reason about.
@Mock private PaymentGateway paymentGateway;
when(paymentGateway.charge(100)).thenReturn(true);
@Spy private OrderCalculator calculator = new OrderCalculator();
doReturn(50.0).when(calculator).applyDiscount(anyDouble());
Q7. How do you use Testcontainers with Spring Boot for integration testing?
Testcontainers is a library that spins up real, disposable Docker containers (PostgreSQL, Kafka, Redis, etc.) for integration tests, so tests run against the actual database engine and SQL dialect used in production instead of an in-memory substitute like H2 that can behave subtly differently.
A container is declared as a static field annotated with @Container, its connection details are wired into Spring's environment via @DynamicPropertySource (or Spring Boot 3.1+'s @ServiceConnection), and the container starts once before all tests in the class and is torn down afterward, giving high-fidelity, isolated integration tests without needing a shared external database.
@SpringBootTest
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void configure(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
}
}
Q8. How do you test exception handling and @ControllerAdvice with MockMvc?
Since @WebMvcTest loads the full MVC layer, including any @ControllerAdvice classes in the tested package, exception handling can be verified by stubbing a mocked service to throw the target exception and then asserting on the response status and body returned by the global exception handler.
This confirms not just that the controller calls the service correctly, but that the entire error-response pipeline — exception thrown, caught by @ExceptionHandler, mapped to the correct HTTP status and JSON error shape — works end-to-end without needing a full @SpringBootTest.
@Test
void getUser_notFound_returns404() throws Exception {
when(userService.findById(99L)).thenThrow(new ResourceNotFoundException("User not found"));
mockMvc.perform(get("/api/users/99"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("User not found"));
}
Q9. What are JUnit 5 lifecycle annotations (@BeforeEach, @BeforeAll, @AfterEach, @AfterAll)?
@BeforeEach runs before every individual test method, typically used to reset test data or re-create mocks so tests don't leak state between each other. @AfterEach runs after every test method, used for cleanup like closing resources.
@BeforeAll runs once before all tests in the class (must be static unless the test class uses @TestInstance(Lifecycle.PER_CLASS)), suited for expensive one-time setup like starting a shared Testcontainer. @AfterAll runs once after all tests complete, used for expensive teardown. Choosing the right scope avoids both redundant setup cost and unwanted state leaking between tests.
class OrderServiceTest {
@BeforeAll
static void setupOnce() { /* start shared resource */ }
@BeforeEach
void setup() { orderService = new OrderService(mockGateway); }
@AfterEach
void tearDown() { reset(mockGateway); }
}
Spring Boot Testing (JUnit, Mockito & Test Slices)
Test Spring boot applications. Master unit test mocks with Mockito, integration tests, and @WebMvcTest slices.
What is the difference between unit testing and integration testing in Spring Boot?
Unit testing verifies a single class or method in complete isolation, with all its dependencies replaced by mo...
What does @SpringBootTest do and when should you use it?
@SpringBootTest bootstraps the complete Spring application context for a test, the same way the real applicati...
What is the difference between @Mock, @MockBean, and @InjectMocks?
@Mock (Mockito) creates a mock object for use in plain unit tests that don't load any Spring context; it's pai...
How does @WebMvcTest work, and how is it different from @SpringBootTest?
@WebMvcTest loads only the Spring MVC layer — controllers, @ControllerAdvice, filters, and MVC-related configu...
What is @DataJpaTest and how does it test the repository layer?
@DataJpaTest loads only the JPA-related components — @Entity classes, Spring Data repositories, and the DataSo...
What is the difference between Mockito's when().thenReturn() and @Spy?
when(mock.method()).thenReturn(value) stubs a method on a full mock object, where every method returns a defau...
How do you use Testcontainers with Spring Boot for integration testing?
Testcontainers is a library that spins up real, disposable Docker containers (PostgreSQL, Kafka, Redis, etc.)...
How do you test exception handling and @ControllerAdvice with MockMvc?
Since @WebMvcTest loads the full MVC layer, including any @ControllerAdvice classes in the tested package, exc...
What are JUnit 5 lifecycle annotations (@BeforeEach, @BeforeAll, @AfterEach, @AfterAll)?
@BeforeEach runs before every individual test method, typically used to reset test data or re-create mocks so...