Spring Junit Controller Mock Not Working - spring

I have the following controller and I want to make a Junit Test on it,
#RequestMapping(value = "/path", method = RequestMethod.Get)
public String getMyPath(HttpServletRequest request, Model model) {
Principal principal = request.getUserPrincipal();
if (principal != null) {
model.addAttribute("username", principal.getName());
}
return "view";
}
The JUnit Method looks as follows:
#Test
public void testGetMyPath() throws Exception {
when(principalMock.getName()).thenReturn("someName");
this.mockMvc.perform(get("/path")).andExpect(status().isOk());
}
principalMock is declared like this:
#Mock
private Principal principalMock;
The problem is that I get NullPointerException at this line on calling getName() method on principal.
model.addAttribute("username", principal.getName());

Your mocking of Principal has absolutely no effect, since it cannot come into play anywhere (the controller is not using any injected dependency to produce the principal, but it uses the HttpServletRequest).
You need to change your test to something like the following:
this.mockMvc.perform(get("/path").principal(principalMock)).andExpect(status().isOk());
This will work because the mock principal will be passed to the MockHttpServletRequest that will actually be passed to the controller method

Related

Spring Security and MockMvc - Need to mock authentication or principal

I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}

Pass parameter to controller

I have some app with JWT authentication. And currently, I have such controller:
#RestController
#RequestMapping("users")
public class UserController {
#PostMapping(value = "{userId}/rate/inc")
public Double incRate(#PathVariable Long userId) {
return service.incUserRate(userId);
}
}
But, I want to get user by the token in the filter and pass it as a method's param. For example:
#PostMapping(value = "/rate/inc")
public Double incRate(User user) {
returnservice.incUserRate(user);
}
Is this possible?
Implement argument resolver and inject into your controller everything you need.
By default Spring allowes you to inject Principal object that by default contains users email (it is default realization in Spring Security). But you can implement injection of your business login users account by implementing Interface HandlerMethodArgumentResolver<User>.
I advice you to create an annotation like #AuthorizedUser in make mark your User param with this annotation. And according to this annotation presence in controller method, inject your user via HandlerMethodArgumentResolver.
#Component
public class UserArgumentHandlerResovler implements HandlerMethodArgumentResolver {
#Autowired
private UserRepository userRepository;
public boolean supportsParameter(MethodParameter parameter) {
return parameter.isAnnotationPresent(AuthorizedUser.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = (String) auth.getPrincipal(); // <- it is a pseudocode, check your Authentication implementation to get email for example.
return userRepository.findByEmail(email);
}
}
If you use Spring Security, you can resolve the current user and then have it provided to your controller method. However – if I'm not mistaken – you must declare it as Principal:
#PostMapping(value = "/rate/inc")
public Double incRate(Principal principal) {
returnservice.incUserRate((User)principal);
}
A more extensive example can be found at Baeldung.

calling a method by class level annotated with #RequestMapping that includes an autowired class

I am trying to call a method that is annotated with #RequestMapping(signIn) through a class level (from method: authentication) like so:
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request)
{
UserController user = new UserController();
return user.signIn(request, null);
}
and my controller looks like:
#Autowired
private UserManager userManager;
#RequestMapping(value = /signin, method = RequestMethod.POST)
public #ResponseBody Response signIn(HttpServletRequest request) {
JsonObject json = Misc.parseJson(request);
String lang = Misc.getLang(request);
user.setEmail(Misc.getEmail(json));
user.setPassword(Misc.getEncryptedPassword(json));
return ResponseUtils.success(userManager.auth(user, lang));
}
user manager is annotated with #component:
#Component
public class UserManager {
public User auth(User user, String lang) {
....
return user;
}
}
Problem is when I call the method "signIn" and just new-up a UserController instance through "/authenticate" mapping, the UserManager becomes NULL. So now I'm assuming that autowiring doesn't work when it's done this way.
Is there any other way to call the signIn method? I would hate to copy paste an already existing code to another class just to get this to work...
Autowiering only works in spring managed bean. If you create a class with new keyword, it is not a spring managed bean and autowiering would not work.
You can try to autowire the class which contains the method which is annotated or better put the code in a service class which can be used by both methods.
It's not problem with #Autowired .There are two type of Annotation
firstly method base annotation and field level annotation. You just used field level annotation.Check your import class with "org.springframework.beans.factory.annotation.Autowired" or it can be problem with initiation of "UserManager"
I don't know why you not moving logic into separate Service classs, but try this:
UserController.java
public UserController(UserManager userManager) {
this.userManager = userManager;
}
and then inside controller where authentication resource method is located:
#Autowired UserManager userManager;
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request) {
UserController user = new UserController(userManager);
return user.signIn(request);
}
So in the end I just separated the logic instead. Though one solution that I tried and I could have used was to just add another mapping to the signIn method instead of adding a new method in the other class since the logic was similar. Still I opted for a separate logic instead since there were a lot of unnecessary code in the signIn method for my purpose.

Having trouble injecting my Spring security user into my controller

I'm using Spring 3.1.0.RELEASE with Spring Security 3.1. I want to inject my Spring user (i.e. the user who is currently logged in) into a controller. I want to do this as opposed to using
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
because it allows me to test the controller more easily with JUnit. However, I'm having a problem with my current setup. My question is, what is the correct way to inject my user (per request) into my controller? In my application context file, I have ...
<bean id="userDetails" class="com.myco.eventmaven.security.SecurityHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy />
</bean>
where I define my factory class as ...
public class SecurityHolder {
#Autowired
private static UserService userService;
public static MyUserDetails getUserDetails() {
final Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
final MyUserDetails reg = (MyUserDetails) a.getPrincipal();
final int userId = reg.getId();
final MyUserDetails foundUser = userService.findUserById(userId);
return foundUser;
} // if
} // getUserDetails
}
but the factory class repeatedly dies because "userService" fails to get autowired (the value is always null). I'm looking for a better way to do all this that can easily also integrate into my JUnit test. Any ideas?
Edit: Here's the JUnit test I'm looking to work with ...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/test/resources/testApplicationContext.xml" })
public class UserEventFeedsControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
...
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() {
...
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
...
#Test
public void testSubmitUserEventFeedsForm() throws Exception {
request.setRequestURI("/eventfeeds.jsp");
request.setMethod("POST");
final List<EventFeed> allEventFeeds = getAllEventFeeds();
request.setParameter("userEventFeeds", allEventFeeds.get(0).getId().toString());
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "user/eventfeeds");
}
You cannot autowire static fields. There are some workarounds, but I don't want to show them to you...
There are plenty of ways to access current user in an easier and more elegant matter:
Inject Principal to your controller (see When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?):
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
UserDetails ud = ((Authentication)principal).getPrincipal()
Develop your custom facade over SecurityContext
Replace built-in contextHolderStrategy in SecurityContextHolder for the purpose of testing
See also
How to get active user's UserDetails
Spring 3 MVC Controller integration test - inject Principal into method

Spring 3 MVC Controller integration test - inject Principal into method

As part of Spring 3 MVC it is possible to inject the currently logged in user (Principle) object into a controller method.
E.g.
#Controller
public class MyController {
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(ModelMap model, Principal principal) {
String name = principal.getName();
... the rest here
}
}
This is documented as part of the Spring 3 documentation here:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments.
This works in the production code. However I don't know how to test this.
When I create an integration test (having set up spring security context as well)
and call the controller handle method then the Principal is always null!
public class FareTypeControllerIntegrationTest extends SpringTestBase {
#Autowired
private MyController controller;
#Autowired
private AnnotationMethodHandlerAdapter handlerAdapter;
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
#Test
public void testUpdate() throws Exception {
request.setRequestURI("/update");
request.setMethod(HttpMethod.POST.name());
... setup rest of request
ModelAndView mav = handlerAdapter.handle(request, response, controller);
.. rest of assertions
}
The tests are running correctly and everything except the Principal is null.
Any ideas?
TIA
Ayub
After a quick look into Spring sources this should work:
request.setUserPrincipal(somePrincipal);
I've tried to do this some time ago, here is the method i used to set up authentication.
protected void setSecurityContext(String login){
userDetailsTest = userManager.loadUserByUsername(login);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userDetailsTest, userDetailsTest.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication((Authentication) testingAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
Then i just call it in the #Before method of the test.
Hope it helps.
I do something like this in my tests prior to calling code using Spring Security (such as the Principal parameter resolver you are testing):
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("wiseau", "Love is blind"));

Resources