I'm trying to use Mockito to test whether when the user hits the login api, it will respond with a JWT token. However, I keep getting the Bad Credentials
error which comes from the authenticationManager.authenticate() method inside Spring Security. I'm now trying to mock this method, but I keep getting a variety of different errors and not sure if my approach is correct. This is my latest implementation, which now fails with You cannot use argument matchers outside of verification or stubbing
as it's not liking how I'm using the mocks.
@WebMvcTest(value = UserCommandController.class, includeFilters = {
// to include JwtUtil in spring context
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtUtil.class)})
class UserCommandControllerTest {
Logger logger = LoggerFactory.getLogger(UserCommandControllerTest.class);
@MockBean
private UserCommandService userCommandService;
@Autowired
private MockMvc mockMvc;
@MockBean
private UserDetailsServiceImpl userDetailsServiceImpl;
@MockBean
private JwtUtil jwtUtil;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
AuthenticationManager authenticationManager;
private static UserDetails dummy;
private static String jwtToken;
@BeforeEach
public void setUp() {
dummy = new User("[email protected]", "123456", new ArrayList<>());
jwtToken = jwtUtil.generateToken(dummy);
}
@Test
void testLoginReturnsJwt() throws Exception {
AuthenticationRequest authenticationRequest = new AuthenticationRequest("[email protected]", "123456");
AuthenticationResponse authenticationResponse = new AuthenticationResponse("anyjwt");
String jsonRequest = asJsonString(authenticationRequest);
String jsonResponse = asJsonString(authenticationResponse);
RequestBuilder request = MockMvcRequestBuilders
.post("/api/adverts/user/login")
.content(jsonRequest)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON);
Authentication authentication = mock(Authentication.class);
authentication.setAuthenticated(true);
when(authentication.isAuthenticated()).thenReturn(true);
when(authenticationManager.authenticate(any())).thenReturn(authentication); // Failing here
when(jwtUtil.generateToken(dummy)).thenReturn("124");
when(userDetailsServiceImpl.loadUserByUsername(eq("[email protected]"))).thenReturn(dummy);
MvcResult mvcResult = mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andExpect(content().json(jsonResponse, true))
.andExpect(jsonPath("$.jwt").value(isNotNull()))
.andReturn();
}
Here is my controller:
@PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> loginUser(@Valid @RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
} catch (BadCredentialsException e) {
throw new Exception("incorrect username or password", e);
}
UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String jwt = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
Thank you.
PS: Here is my repo and where AuthenticationManager is set: https://github.com/francislainy/adverts-backend/blob/dev_jwt/src/main/java/com/example/adverts/MyWebSecurity.java#L30
CodePudding user response:
You need the AuthenticationManager
to be a mock and it's not in your case (@Autowired
injects a "real" instance). You can mock beans using MockBean annotation:
@MockBean
AuthenticationManager authenticationManager;
Now you can mock authenticationManager
all you want.
Another problem with your code is that you are misusing the ArgumentMatchers.isNotNull()
matcher in your assertions causing an exception:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced or misused argument matcher detected here:
-> at com.example.adverts.controller.user.UserCommandControllerTest.testLoginReturnsJwt(UserCommandControllerTest.java:356)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(any());
verify(mock).someMethod(contains("foo"))
This message may appear after an NullPointerException if the last matcher is returning an object
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
when(mock.get(any())); // bad use, will raise NPE
when(mock.get(anyInt())); // correct usage use
Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.
ArgumentMatchers provides matchers to be used in method call stubbing and verification. You should use org.hamcrest.CoreMatchers#notNullValue() instead.
With these fixes your test passes all in green.