Spring Testing in Java
Introduction
In the lifecycle of enterprise software development, testing is what separates fragile code from resilient, maintainable systems. Spring Testing provides a robust framework that integrates seamlessly with testing libraries like JUnit and Mockito, allowing developers to write everything from fast unit tests to comprehensive integration tests.
Spring's TestContext framework abstracts away the complexity of loading application contexts and managing beans during test execution, ensuring that your tests are realistic and reliable.
Core Annotations and Strategies
Spring provides different annotations tailored for specific layers of your application. This "sliced testing" approach ensures that you only load the beans necessary for the component being tested, keeping test execution fast.
@SpringBootTest: Loads the full application context. Used for broad integration testing.@WebMvcTest: Loads only the web layer (Controllers, Filters, ControllerAdvice). Used to test REST endpoints usingMockMvc.@DataJpaTest: Loads only the JPA/database components. Automatically configures an in-memory database and wraps tests in a transaction.@MockBean: Creates and injects a Mockito mock into the Spring Application Context, replacing any existing bean of the same type.
Code Examples
1. Unit Testing a Service (Without Spring Context)
For pure business logic, you don't need the Spring context. Fast, standard Mockito + JUnit 5 is best.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testGetUser() {
// Arrange
User mockUser = new User(1L, "Alice");
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
// Act
User result = userService.getUserById(1L);
// Assert
assertEquals("Alice", result.getName());
verify(userRepository, times(1)).findById(1L);
}
}
2. Testing the Web Layer with @WebMvcTest
Use MockMvc to perform HTTP requests against your controllers without starting a real HTTP server.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService; // Mocking the dependency
@Test
public void testGetEndpoint() throws Exception {
when(userService.getUserById(1L)).thenReturn(new User(1L, "Bob"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Bob"));
}
}
3. Database Integration Testing with @DataJpaTest
Test your repository queries against an actual (usually in-memory) database.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSaveAndFind() {
User user = new User("Charlie");
userRepository.save(user);
User found = userRepository.findByName("Charlie");
assertNotNull(found);
assertEquals("Charlie", found.getName());
}
}
Advanced: Testcontainers
While in-memory databases like H2 are great for speed, they often lack database-specific features (e.g., PostgreSQL JSONB columns). Testcontainers allows you to spin up lightweight, throwaway instances of real databases via Docker directly from your JUnit tests.
By using @Testcontainers and @Container, Spring Boot can seamlessly connect to a real PostgreSQL container just for the duration of the test suite, guaranteeing that your integration tests behave exactly like production.
Summary
- Use standard JUnit/Mockito for fast unit tests of business logic.
- Utilize Sliced Testing (
@WebMvcTest,@DataJpaTest) to isolate layers and keep tests performant. - Use
@SpringBootTestonly when testing the complete end-to-end integration of your application. - Explore Testcontainers for the highest fidelity database integration tests.