Testing Spring Boot Security simply - java

I'm struggling with testing access control on URLs protected by Spring Security.
The configuration looks like this:
http
.authorizeRequests()
.antMatchers("/api/user/**", "/user").authenticated()
.antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasAuthority("ADMIN")
.anyRequest().permitAll();
And the test class looks like this:
package com.kubukoz.myapp;
import com.kubukoz.myapp.config.WebSecurityConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import javax.transaction.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {MyApplication.class, WebSecurityConfig.class})
#WebAppConfiguration
#TransactionConfiguration(defaultRollback = true)
#Transactional(rollbackOn = Exception.class)
public class MyApplicationTests {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Autowired
private FilterChainProxy filterChainProxy;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.dispatchOptions(true)
.addFilters(filterChainProxy)
.build();
}
#Test
public void testAnonymous() throws Exception {
mockMvc.perform(get("/api/user/account")).andExpect(status().is3xxRedirection());
}
#Test
public void testUserAccessForAccount() throws Exception{
mockMvc.perform(get("/api/user/account")).andExpect(status().isOk());
}
}
What's the easiest way to make the last two tests pass?
#WithMockUser didn't work.

You should not add the FilterChainProxy directly. Instead, you should apply SecurityMockMvcConfigurers.springSecurity() as indicated by the reference. An example is included below:
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
The result of this is:
the FilterChainProxy is added as a Filter to MockMvc (as you did)
the TestSecurityContextHolderPostProcessor is added
Why is TestSecurityContextHolderPostProcessor necessary? The reason is that we need to communicate the current user from the test method to the MockHttpServletRequest that is created. This is necessary because Spring Security's SecurityContextRepositoryFilter will override any value on SecurityContextHolder to be the value found by the current SecurityContextRepository (i.e. the SecurityContext in HttpSession).
Update
Remember anything that contains role in the method name automatically prefixes "ROLE_" to the string that was passed in.
Based on your comment, the problem is you need to either update your configuration to use hasRole instead of hasAuthority (since your annotation is using roles):
.authorizeRequests()
.antMatchers("/api/user/**", "/user").authenticated()
.antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasRole("ADMIN")
.anyRequest().permitAll();
Alternatively
You in Spring Security 4.0.2+ you can use:
#WithMockUser(authorities="ADMIN")

Okay, figured it out.
mockMvc.perform(get("/api/user/account")
.with(user("user")))
.andExpect(status().isOk());
It works now.

Related

Spring boot WebMvcTest cases keep getting terminated and won't run

When I try using the below code to test the controllers no errors happen, it just says terminated with no logged messages or anything.
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import ....controllers.UserController;
import ....data.response.UserResponse;
import ....models.user.User;
#RunWith(SpringRunner.class)
#WebMvcTest(UserController.class)
public class UserWebMvc {
#Autowired
private MockMvc mvc;
#Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray() throws Exception {
User alex = new User();
List<UserResponse> allUsers = Arrays.asList(new UserResponse(alex.getId(), alex.getInfo()));
RequestBuilder request = MockMvcRequestBuilders.get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals(result.getResponse().getContentAsString(), "hello");
// userService.createUser(
// new UserRequest(alex.getInfo().getName(), alex.getInfo().getEmail(), alex.getInfo().getPassword()));
//
}
}
However my test to check that junit is working runs fine, so I'm thinking its something to do with SpringRunner or WebMvc
#SpringBootTest
class BackendApplicationTests {
#Test
void contextLoads() {
assertTrue(false);
}
}
Isn't assertTrue(false) always going to be false? You are basically checking whether false equals true, which will always fail.
Also, from the Spring documentation regarding #WebMvcTest:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
Using this annotation, it will not scan any beans from the service layer. So if you have a UserService which is injected into the UserController, it will not be found by Spring.
When I run tests like this, I would do something like:
class UserControllerTest {
#InjectMocks
private UserController userController;
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMvc =
MockMvcBuilders.standaloneSetup(userController).build();
}
#Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
throws Exception {
User alex = new User();
List<UserResponse> allUsers = Arrays.asList(new
UserResponse(alex.getId(), alex.getInfo()));
RequestBuilder request = MockMvcRequestBuilders.get("/hello");
MvcResult result = mvc.perform(request).andReturn();
assertEquals(result.getResponse().getContentAsString(),
"hello");
}
}
I was using the wrong import. Changing to this fixed it
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Spring Cloud Contract not executing #Before

I am writing tests using Spring Cloud Contract.
In my configuration I am using for test an in memory DB that is created and destroyed at each test run.
In order to test successfully I need to put some data in the DB but it seems like the classic #Before annotation used in jUnit does not work (the code in it is never executed).
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.context.WebApplicationContext;
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class BaseTestClass {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseTestClass.class);
#Autowired
EntityRepository entityRepository;
#Autowired
private WebApplicationContext context;
#Before
public void init() {
// This code never runs
LOGGER.info("Init started");
MyEntity entity1 = new MyEntity();
entityRepository.save(entity1);
}
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}
What am I missing?
#Before belongs to JUnit4 and earlier while #BeforeEach belongs to JUnit5 as a replacement and clarification of #Before. If you are running with JUnit5, perhaps put all your initialization logic in #BeforeEach (or if it's a one-time init, in #BeforeAll)
I've found a solution that works.
I was adding the #TestInstance decorator incorrectly, here's my working solution:
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.context.WebApplicationContext;
#ExtendWith(SpringExtension.class)
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class BaseTestClass {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseTestClass.class);
#Autowired
EntityRepository entityRepository;
#Autowired
private WebApplicationContext context;
#BeforeAll
public void init() {
LOGGER.info("Init started");
MyEntity entity1 = new MyEntity();
entityRepository.save(entity1);
}
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}

How to test mvc controller method with http response argument?

Can you help me? I need to test this controller method. But don't know what to do with httpservletresponse object.
#Controller
public class HomeController {
#PostMapping("/signout")
public String signOut(HttpServletResponse response){
response.addCookie(new Cookie("auth-token", null));
return "redirect:http://localhost:3000";
}
}
Thank you)
Spring MVC Test provides an effective way to test controllers by performing requests and generating responses through the actual DispatcherServlet.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
#RunWith(SpringRunner.class)
#WebMvcTest(controllers=HomeController.class)
public class HomeControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testSignOut() throws Exception {
mockMvc.perform(post("/signout"))
.andDo(print())
.andExpect(new ResultMatcher() {
#Override
public void match(MvcResult result) throws Exception {
Assert.assertEquals("http://localhost:3000",result.getResponse().getRedirectedUrl());
}
});
}
}
In case of Spring MVC without spring boot, use standalone MockMvc support
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration // or #ContextConfiguration
public class HomeControllerTest{
#Autowired
private HomeController homeController;
private MockMvc mockMvc;
#Before
public void setup() {
// Setup Spring test in standalone mode
this.mockMvc =
MockMvcBuilders.standaloneSetup(homeController).build();
}

Unit testing a AWS Spring Boot Configuration file - possible?

We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}

Spring MVC test with MockMvc

I'm trying to run a test for testing a Spring MVC controller. The test compile and runs, but my problem is that I got a PageNotFound warning:
WARN PageNotFound - No mapping found for HTTP request with URI [/] in DispatcherServlet with name ''
My really simple test as follows:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({
"classpath*:/WEB-INF/applicationContext.xml",
"classpath*:/WEB-INF/serviceContext.xml"
})
public class FrontPageControllerTest {
#Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
#Before
public void init() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.ctx).build();
}
#Test
public void frontPageController() throws Exception {
this.mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("searchfrontpage"));
}
}
I'm 100% sure that my webapp maps to the frontpage at "/" and that the name on the view is "searchfrontpage".
Please help!
Another easier way to solve the issue is to change init to this :
mockMvc = MockMvcBuilders.standaloneSetup(new FrontPageController()).build();
My ContextConfiguration was wrong. Correct was:
#ContextConfiguration({
"file:src/main/webapp/WEB-INF/applicationContext.xml",
"file:src/main/webapp/WEB-INF/serviceContext.xml"
})
Now everything works fine.

Categories