Fix: Spring Boot Test Not Working — ApplicationContext Fails to Load, MockMvc Returns 404, or @MockBean Not Injected
Quick Answer
How to fix Spring Boot test issues — @SpringBootTest vs test slices, MockMvc setup, @MockBean vs @Mock, test context caching, and common test configuration mistakes.
The Problem
The test context fails to load:
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService'Or MockMvc returns 404 for routes that exist:
@Test
void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk());
// Fails: expected 200 but was 404
}Or @MockBean doesn’t inject into the controller:
@MockBean
private UserService userService;
// userService.findById() still calls the real databaseOr tests pass individually but fail when run together:
Tests run: 15, Failures: 3, Errors: 0, Skipped: 0
[WARNING] Tests run in an unexpected orderWhy This Happens
Spring Boot tests have multiple annotations with different scopes, and picking the wrong one is the most common source of confusion:
@SpringBootTestloads the full context — it starts the entire Spring application, including all beans, datasources, and auto-configurations. This is slow and overkill for unit tests.- Test slices load only part of the context —
@WebMvcTestloads only web layer beans;@DataJpaTestloads only JPA components. Using the wrong slice means your beans aren’t loaded, causing404or unsatisfied dependencies. @MockBeanreplaces beans in the Spring context — it’s different from Mockito’s@Mock.@MockBeanregisters a mock in the Spring context;@Mockonly creates a Mockito mock without Spring integration.- Context caching — Spring caches test contexts across tests. Modifying the context (e.g., using
@DirtiesContextor@MockBeanin different tests) creates new contexts and slows test suites.
Fix 1: Choose the Right Test Annotation
Match the annotation to what you’re actually testing:
// @SpringBootTest — full application context
// Use for: integration tests that need the full stack
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
@Autowired
private TestRestTemplate restTemplate; // Makes real HTTP calls
@Test
void testFullFlow() {
ResponseEntity<User> response = restTemplate.getForEntity("/api/users/1", User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
// @WebMvcTest — web layer only (controllers, filters, @ControllerAdvice)
// Use for: testing controllers without starting the server or loading service/repository beans
@WebMvcTest(UserController.class) // Load only UserController
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean // Must mock all dependencies that UserController injects
private UserService userService;
@Test
void testGetUser() throws Exception {
when(userService.findById("1")).thenReturn(new User("1", "Alice"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"));
}
}
// @DataJpaTest — JPA/database layer only
// Use for: testing repositories with an in-memory database
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testFindByEmail() {
userRepository.save(new User(null, "Alice", "[email protected]"));
Optional<User> found = userRepository.findByEmail("[email protected]");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}
// @Service / plain Mockito — pure unit test, no Spring context at all
// Use for: testing service logic in isolation — fastest option
class UserServiceTest {
@Mock
private UserRepository userRepository; // Mockito mock, not Spring
@InjectMocks
private UserService userService; // Injects mocks directly
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
}
@Test
void testCreateUser() {
when(userRepository.save(any())).thenReturn(new User("1", "Alice", "[email protected]"));
User created = userService.createUser("Alice", "[email protected]");
assertThat(created.getId()).isEqualTo("1");
}
}Fix 2: Set Up MockMvc Correctly
MockMvc can be configured in two ways — through @WebMvcTest (recommended) or manually:
// Method 1: @WebMvcTest — Spring creates MockMvc automatically
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private JwtTokenProvider jwtTokenProvider; // Mock all dependencies
@Autowired
private ObjectMapper objectMapper; // For JSON serialization
@Test
void testCreateUser() throws Exception {
CreateUserRequest request = new CreateUserRequest("Alice", "[email protected]");
User created = new User("1", "Alice", "[email protected]");
when(userService.createUser(any(CreateUserRequest.class))).thenReturn(created);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value("1"))
.andExpect(jsonPath("$.name").value("Alice"))
.andDo(print()); // Prints request/response to console — useful for debugging
}
@Test
void testGetUserNotFound() throws Exception {
when(userService.findById("999")).thenThrow(new UserNotFoundException("999"));
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").value("User 999 not found"));
}
}
// Method 2: MockMvcBuilders.standaloneSetup — no Spring context at all
// Use when you don't need Spring's filter chain or exception handlers
class UserControllerStandaloneTest {
private MockMvc mockMvc;
@Mock
private UserService userService;
@BeforeEach
void setup() {
MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(new UserController(userService))
.setControllerAdvice(new GlobalExceptionHandler()) // Add manually
.build();
}
}Fix 3: Fix @MockBean vs @Mock Confusion
Use @MockBean when the mock needs to be in the Spring context, @Mock for pure unit tests:
// @WebMvcTest — use @MockBean (Spring manages injection)
@WebMvcTest(UserController.class)
class ControllerTest {
@MockBean
private UserService userService; // Replaces UserService bean in context
// Spring injects this into UserController automatically
}
// Plain unit test — use @Mock + @InjectMocks (Mockito manages injection)
@ExtendWith(MockitoExtension.class) // Use this instead of MockitoAnnotations.openMocks()
class ServiceTest {
@Mock
private UserRepository userRepository; // Mockito mock
@InjectMocks
private UserService userService; // Mockito injects @Mock fields into this
@Test
void testFindUser() {
when(userRepository.findById("1")).thenReturn(Optional.of(new User("1", "Alice")));
User user = userService.findById("1");
assertThat(user.getName()).isEqualTo("Alice");
verify(userRepository, times(1)).findById("1");
}
}Spy on real beans:
@WebMvcTest(UserController.class)
class ControllerTest {
@SpyBean // Uses the real bean but allows stubbing specific methods
private UserService userService;
@Test
void testWithSpy() {
// Only stub the method you want to intercept
doReturn(new User("1", "Alice")).when(userService).findById("1");
// All other methods use the real implementation
}
}Fix 4: Configure Test Properties
Override application properties for tests without polluting the main config:
// Method 1: @TestPropertySource — override specific properties
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"app.feature.enabled=false",
"spring.mail.host=localhost"
})
class ApplicationTest { }
// Method 2: application-test.yml in src/test/resources
// Activated with @ActiveProfiles("test")
@SpringBootTest
@ActiveProfiles("test")
class ProfileTest { }
// src/test/resources/application-test.yml:
// spring:
// datasource:
// url: jdbc:h2:mem:testdb
// jpa:
// show-sql: true# src/test/resources/application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
mail:
host: localhost
port: 3025 # FakeSMTP or GreenMail
app:
jwt:
secret: test-secret-key-for-testing-only
feature-flags:
new-ui: falseUse @DynamicPropertySource for Testcontainers:
@SpringBootTest
@Testcontainers
class ContainerTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testWithRealDatabase() {
// Uses a real PostgreSQL container
userRepository.save(new User(null, "Alice", "[email protected]"));
assertThat(userRepository.count()).isEqualTo(1);
}
}Fix 5: Test Security Configuration
Testing secured endpoints requires configuring the security context:
@WebMvcTest(UserController.class)
@Import(SecurityConfig.class) // Import security config if needed
class SecuredControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private UserDetailsService userDetailsService;
@MockBean
private JwtAuthenticationFilter jwtFilter;
// Test with @WithMockUser — simulates authenticated user
@Test
@WithMockUser(username = "alice", roles = {"USER"})
void testGetProfile() throws Exception {
when(userService.findByUsername("alice")).thenReturn(new User("alice"));
mockMvc.perform(get("/api/profile"))
.andExpect(status().isOk());
}
// Test with @WithMockUser with admin role
@Test
@WithMockUser(roles = {"ADMIN"})
void testAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
// Test unauthorized access
@Test
void testUnauthenticated() throws Exception {
mockMvc.perform(get("/api/profile"))
.andExpect(status().isUnauthorized());
}
// Test with JWT token in header
@Test
void testWithJwtToken() throws Exception {
String token = generateTestToken("alice", List.of("ROLE_USER"));
mockMvc.perform(get("/api/profile")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
}Fix 6: Speed Up Tests with Context Caching
Spring caches test contexts — avoid breaking the cache unnecessarily:
// BAD — each class with different @MockBean creates a new context
@WebMvcTest(UserController.class)
class UserControllerTest {
@MockBean UserService userService; // Context A
}
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@MockBean OrderService orderService; // Context B — different from A
@MockBean UserService userService; // Adding this means different context
}
// BETTER — share context by using the same set of mocks
// Create a base test class with all common mocks
@WebMvcTest
@Import({UserController.class, OrderController.class})
abstract class BaseControllerTest {
@MockBean UserService userService;
@MockBean OrderService orderService;
// Subclasses share this context
}
class UserControllerTest extends BaseControllerTest { }
class OrderControllerTest extends BaseControllerTest { }Use @DirtiesContext sparingly:
// @DirtiesContext forces a new context — use only when necessary
@DirtiesContext // Marks context as dirty after this test class
class DatabaseModifyingTest {
// Only use if you've genuinely modified the context
}
// Better alternative: use transactions to roll back after each test
@DataJpaTest
@Transactional // Each test runs in a transaction, rolled back after
class RepositoryTest {
@Autowired UserRepository userRepository;
@Test
void testSave() {
userRepository.save(new User(null, "Alice"));
// Transaction rolls back after test — no cleanup needed
}
}Still Not Working?
Context fails to load due to missing beans — @WebMvcTest only loads the web layer. If your controller injects a service that injects a repository that needs a DataSource, and you only have @WebMvcTest, Spring can’t find those beans. Add @MockBean for every dependency the controller needs, or use @SpringBootTest for integration tests.
Tests pass individually but fail together — shared static state, Mockito mocks not being reset between tests, or database state leaking between tests. Add @Transactional on test classes to auto-rollback, or explicitly call Mockito.reset(mock) in @AfterEach. For @WebMvcTest, Mockito mocks created with @MockBean are automatically reset after each test.
@Value fields are null in tests — when using standaloneSetup without Spring context, @Value fields aren’t injected. Either use ReflectionTestUtils.setField(controller, "fieldName", value) or use @WebMvcTest which loads the Spring context.
@Async methods execute synchronously in tests — Spring’s @Async requires a proxy, which only exists in the full context. In unit tests, async methods are called directly. If you need to test async behavior, use @SpringBootTest with the full context and wait for completion with Awaitility.
For related Spring issues, see Fix: Spring Boot Transaction Not Rolling Back and Fix: Spring Cache Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Supertest Not Working — Requests Not Sending, Server Not Closing, or Assertions Failing
How to fix Supertest HTTP testing issues — Express and Fastify setup, async test patterns, authentication headers, file uploads, JSON body assertions, and Vitest/Jest integration.
Fix: Go Test Not Working — Tests Not Running, Failing Unexpectedly, or Coverage Not Collected
How to fix Go testing issues — test function naming, table-driven tests, t.Run subtests, httptest, testify assertions, and common go test flag errors.
Fix: Java Record Not Working — Compact Constructor Error, Serialization Fails, or Cannot Extend
How to fix Java record issues — compact constructor validation, custom accessor methods, Jackson serialization, inheritance restrictions, and when to use records vs regular classes.
Fix: OpenTelemetry Not Working — Traces Not Appearing, Spans Missing, or Exporter Connection Refused
How to fix OpenTelemetry issues — SDK initialization order, auto-instrumentation setup, OTLP exporter configuration, context propagation, and missing spans in Node.js, Python, and Java.