When writing tests for Spring Security, it’s important to align your test scope with the actual application flow. Here’s how to test your CustomUserDetailService
correctly in a Spring Boot microservices setup that communicates with another service via RestTemplate
and is wired into the AuthenticationManager
.
✅ Why Testing AuthenticationManager
Is Better Than Calling loadUserByUsername()
Calling authenticationManager.authenticate(...)
:
✅ Tests the full Spring Security flow.
✅ Verifies that
AuthenticationManager
delegates to yourCustomUserDetailService
.✅ Validates Spring Security configurations like password encoders and roles.
✅ Justifies the use of
@SpringBootTest
by requiring a real security context.
Calling customUserDetailService.loadUserByUsername()
directly:
❌ Only tests one method in isolation.
❌ Does not verify integration with security chain.
✅ Could be written as a unit test without Spring.
📅 Test Setup Summary
Service Under Test: CustomUserDetailService
Registered as a
@Service
Wired into
AuthenticationManager
via a customAuthenticationProvider
Loads user data via
RestTemplate
fromMEMBER-SERVICE
Dependencies
RestTemplate
is mocked withMockRestServiceServer
Test uses
@SpringBootTest
,@AutoConfigureWebClient
, and a test profile
🖊️ Example Test Code: AuthenticationManager
Test (Spring Security Context)
@SpringBootTest
@AutoConfigureWebClient
@Import(RestTemplateTestConfig.class)
class AuthenticationFlowIT {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
@BeforeEach
void setup() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
void authenticationManager_shouldAuthenticate_whenValidUserExists() {
String email = "test@example.com";
String password = "securePassword";
MemberDTO mockMember = new MemberDTO();
mockMember.setEmail(email);
mockMember.setPassword(password);
mockMember.setRole("ROLE_USER");
ServiceResponse<MemberDTO> response = new ServiceResponse<>();
response.setSuccess(true);
response.setData(mockMember);
String json;
try {
json = new ObjectMapper().writeValueAsString(response);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
mockServer.expect(requestTo("http://MEMBER-SERVICE/member/findMemberByEmail"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess(json, MediaType.APPLICATION_JSON));
Authentication result = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(email, password)
);
assertThat(result.isAuthenticated()).isTrue();
assertThat(result.getName()).isEqualTo(email);
mockServer.verify();
}
}
🧪 Additional Comparison: loadUserByUsername()
Direct Unit Test
class CustomUserDetailServiceUnitTest {
private RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
private CustomUserDetailService customUserDetailService = new CustomUserDetailService(restTemplate);
@Test
void loadUserByUsername_shouldReturnValidUserDetails_whenMocked() {
String email = "test@example.com";
MemberDTO mockMember = new MemberDTO();
mockMember.setEmail(email);
mockMember.setPassword("securePassword");
mockMember.setRole("ROLE_USER");
Mockito.when(restTemplate.postForObject(
Mockito.eq("http://MEMBER-SERVICE/member/findMemberByEmail"),
Mockito.eq(email),
Mockito.eq(MemberDTO.class)))
.thenReturn(mockMember);
UserDetails result = customUserDetailService.loadUserByUsername(email);
assertThat(result.getUsername()).isEqualTo(email);
assertThat(result.getPassword()).isEqualTo("securePassword");
assertThat(result.getAuthorities()).extracting("authority").containsExactly("ROLE_USER");
}
}
⚠️ Common Pitfalls
❌ Calling
loadUserByUsername()
directly: Only valid for pure unit tests, not for validating authentication flow.❌ Using
@SpringBootTest
unnecessarily: Avoid full context startup unless you’re testing actual Spring features.❌ Skipping mock verification: Always call
mockServer.verify()
to ensure the mock request was made.
✨ Conclusion
To write meaningful Spring Security tests:
Use
authenticationManager.authenticate(...)
to simulate real loginMock external service calls if needed (like member lookup)
Justify your use of
@SpringBootTest
with actual Spring behavior (e.g. security context)Understand the flow from
AuthenticationManager
→AuthenticationProvider
→UserDetailsService
This approach gives confidence that your custom login logic works as intended during real user authentication.