I am writing unit test cases for Controller layer. I have a call where i am getting user from Spring SecurityContextHolder. When i run my test case i get Null pointer exception because I don't know how to mock Spring security context.
Below is my code, any suggestion how to do it?
Controller Methhod:
#RequestMapping(method = RequestMethod.POST)
public void saveSettings(#RequestBody EmailSettingDTO emailSetting) {
User user = ((CurrentUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
settings.saveUserEmailSetting(user, emailSetting);
}
My Test case :
#Test public void testSaveSettings() throws Exception {
mockMvc.perform(post(BASE_URL).content(this.objectMapper.writeValueAsString(emailDto))
.contentType(MediaTypes.HAL_JSON)).andExpect(status().isOk());
}
There is a Spring Security Test library for this purpose.
You can use #WithMockUser to achieve this. See the post
You can use #WithUserDetails
this annotation can be added to a test method to emulate running with a UserDetails returned from the UserDetailsService.
By using this, you create a context to run a test in, for example:
#Test
#WithUserDetails("admin")
public void testAdmin() throws Exception {
mockMvc.perform(...);
}
This will execute testAdmin() with the SecurityContext of admin.
But please note, in order to use this; there must be a User persisted with the name admin, otherwise you will get result exceptions.
Related
I have the following repository:
#Repository
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findUserByUniqueName(String uniqueName);
}
In an integration test, first, I change the uniqueName field of a user entity and save it to database. then I call an endpoint with mockMvc and add the oldUniqueName to the headers of the request.
#Test
#Transactional
void test() {
User user =
userRepository.findUserByUniqueName("oldUniqueName").orElse(null);
assertThat(user).isNotNull();
user.setUniqueName("newUniqueName");
userRepository.saveAndFlush(user);
headers = ... // adding oldUniqueName to the headers
mockMvc
.perform(get(endpointUrl).headers(headers))
.andExpect(status().isUnauthorized());
}
The request is then intercepted by a custom security filter to do some modifications in the security context. in this filter I extract the UniqueName from the request header and try to find the corresponding user in database:
#Override
#Transactional
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String oldUniqueName = extractUniqueNameFromRequest(request);
try {
User user = userRepository.findUserByUniqueName(oldUniqueName).orElseThrow(new Exception());
// update security context
}
catch(Exception e) {
// handle exception
}
}
I am expecting that the call to the repository with the oldUniqueName returns an empty Optional and therefore an exception is thrown. However, something strange happens. Although I am querying the database with a non existing uniqueName (oldUniqueName), the User Entity is found but it's uniqueName field has the value "newUniqueName". I guess it has something to do with transactions or maybe cache but can't figure it out. Does anybody know what I am doing wrong?
I'm not completely sure, but it looks like the mock mvc request runs in it's own thread and therefore transaction. Since you didn't commit the transaction of your integration test, mock mvc still sees the old data.
If this assumption is true you should be able to fix it by putting the first part of your integration test in a separate transaction.
You can do this by removing the #Transactional annotation and use TransactionTemplate instead.
I want to test the response of my request.
I have a controller like this,
#RequestMapping("/user")
public class StandardController(#RequestBody Object body) {
#PostMapping(path=/info")
public ResponseEntity<String> getUserInfo(#Validated #RequestBody CustomDTO customDTO) throws customException {
try {
//process something with customDTO
} catch (Exception e) {
//throw exception
}
}
}
Now I have made one of the properties of CustomDTO as #NotNull. When I test the endpoint through Postman I will successfully get 400 as expected if I supply the required field as null value. But how do I test this scenario with Mockito?
If you want to test Spring MVC controllers, don't use unit tests; you need to ensure that everything works correctly including mappings, validation, and serialization. Use MockMvc (in regular, not standalone mode), and if you like you can use Mockito to mock service dependencies.
If you want to test your controller, you have two categories of test:
Unit test by using spring test slice.
Integration test
Read this post also for more understanding.
I create REST web-service with Spring Boot.
I would like to know what is a better way to handle exceptions in a controller. I have seen other questions and didn’t found an answer.
My controller:
#GetMapping
public ResponseEntity<?> saveMyUser(){
MyUser myUser = new MyUser(“Anna”);
//throws SQLException
MyUserDetails userDetails = userService.saveMyUser(myUser);
//if successful
return ResponseBody.ok(userDetails);
}
saveMyUser() method of UserService:
public MyUserDetails saveUser(MyUser) throws SQLException {...}
So at this point I have at least 2 simple options:
Add exception to method signature.
Here I may rely on Spring Boot to pass all information about exception and status code to a client. However do not know if it is a reliable approach.
Surround with try/catch and pass all information about exceptions manually.
What is a better simple way?
You can create an additional class with #ControllerAdivce annotation and later you will be able to write custom response logic for each exception e.g:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({SQLException.class})
public ResponseEntity<Object> sqlError(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Some SQL exception occured");
}
}
Also, you can extend ResponseEntityExceptionHandler and override the default behavior for mapping from exceptions to HTTP response.
Also, take a look at this, it holds very usefull information for your case.
I want to test the login process in a Spring Boot Application using MockMvc. After the successful login, the user gets redirected to /home. To test this, I use:
#Test
public void testLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
}
This test delivers the expected results.
In addition, I must test the HTTP status code of the redirected page (/home). Lets say the /home-page returns an HTTP 500 internal Server error, I need to be able to test this.
I tried the following:
#Test
public void testLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
mockMvc.perform(get("/home").with(csrf())).andExpect(status().isOk());
}
Instead if getting a 200 or a 500 (in case of an error), I get the status code 302.
Is there any way to correctly test the HTTP status code when following a redirect-URL?
Thanks and best regards
First, I would split your test into 2 separate tests, because you are testing 2 quite different scenarios:
#Test
public void testSuccessfulLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
}
#Test
public void testHomepageThrows500() throws Exception {
// configure a mock service in the controller to throw an exception
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().is5xxServerError());
}
Your first test is that of the successful login scenario.
The second test, as you've worded it in your question, is that where the home page (assuming a controller) returns a HTTP 500.
To get to the home page, you still need to login - it's not the act of logging on that generates the error, its the controller itself once you've logged on.
To make the controller return a HTTP 500 you are going to need to simulate some error. Without seeing your controller, I can only guess there is some service that is injected in. In your test you should be able to provide a mock of that, and then configure the mock to throw an exception.
You should be able to inject a mock something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(HomeController.class)
public class HomeControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private YourService yourService;
And then within your test do something like the following (I'm using the BDD methods of mockito):
#Test
public void testHomepageThrows500() throws Exception {
given(yourService.someMethod()).willThrow(new Exception("something bad happened");
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().is5xxServerError());
}
I've got java Spring Security app (built with jhipster) and I'm trying to add some unit tests that test logic based on the current authenticated user.
To do that i've configured my MockMvc object to use a web application context and spring security as so:
#Test
#Transactional
public void getAllContacts() throws Exception {
restContactMockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
restContactMockMvc.perform(get("/api/contacts")).andExpect(status().isOk());
}
The problem is that I've also configured the app to require HTTPS, and whenever I run this unit test I get a 302 redirect response since it appears to be trying to send an HTTP request instead of HTTPS.
The way I've enforced HTTPS is with the following line in SecurityConfiguration.configure(HttpSecurity http):
if (env.acceptsProfiles("!dev"))http.requiresChannel().anyRequest().requiresSecure();
So, my question is how can I get my unit test to run correctly? Probably either by sending a mock HTTPS request, or by disabling HTTPS in the case of running a unit test?
Using Springs MockMvc stuff you can also specify that you want to send a mock HTTPS request using the secure(true) method as follows:
restContactMockMvc.perform( get("/api/contacts").secure( true ) ).andExpect( status().isOk() )
I'm answering my own question with the fix that i've started using, in hopes that this can help others. I'm still open to other better solutions.
In SecurityConfiguration.configure(HttpSecurity http) method, i've modified the if statement for enforcing https to skip it if we are running an integrationTest:
if (env.acceptsProfiles("!dev") && !((StandardEnvironment) env).getPropertySources().contains("integrationTest")) {
http.requiresChannel().anyRequest().requiresSecure();
}
In addition to the correct answer of #rhinds you can also add this code to your #Before method to have all calls be secure by default:
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.testSecurityContext;
public class YourTestClass {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private SecurityTestController securityTestController;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = standaloneSetup(securityTestController)
.addFilters(springSecurityFilterChain)
.defaultRequest(get("/").secure(true).with(testSecurityContext()))
.build();
}
// Your Tests Here
#Test
public void httpsRedirect() throws Exception {
mockMvc.perform(get("/").secure(false))
.andExpect(status().isFound())
.andExpect(result -> StringUtils.isNotEmpty(result.getResponse().getHeader("Location")))
.andExpect(result -> result.getResponse().getHeader("Location").startsWith("https://"));
}
}
With a bad configuration, Spring Security can change response status to "302 FOUND". Double check your security config.
#Bean
#Throws(Exception::class)
fun authTokenFilterBean(): CustomAuthTokenFilter {
val authTokenFilter = CustomAuthTokenFilter()
// if you don't have these lines, add them:
authTokenFilter.setAuthenticationSuccessHandler(MyAuthSuccessHandler())
authTokenFilter.setAuthenticationFailureHandler(MyAuthFailureHandler())
return authTokenFilter
}
where
CustomAuthTokenFilter is your custom implementation of AbstractAuthenticationProcessingFilter;
MyAuthSuccessHandler and MyAuthFailureHandler are handlers provided by you. They can be empty, or they can contain your custom logic.