How to Junit a RestController But with Spring Mobile (spring-mobile-device) - java

I have a Rest controller with a Device (Device must be resolvem, I'm using spring-mobile-device) as a Parameter. The unit test gave me a status 415.
Here is the Code of
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequestDto authenticationRequest,
Device device) throws AuthenticationException {
Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String token = this.tokenGenerator.generateToken(userDetails, device);
return ResponseEntity.ok(new AuthenticationResponseDto(token));
}
Unit test
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
res.andExpect(status().isOk());

Well basically I was wrong with my configuration. It is mandatory configure the Web Config for testing in same way that production configuration but are grammatically different. Well I learned a lot about MockMVC config with this problem.
Here's the solution if you want do unit testing with spring mobile.
First Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebTestConfig.class})
#WebAppConfiguration
public class WebTestConfigAware {
#Autowired
private WebApplicationContext context;
protected MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
DeviceResolverRequestFilter deviceResolverRequestFilter = new DeviceResolverRequestFilter();
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(this.springSecurityFilterChain, deviceResolverRequestFilter).build();
}
}
Second class
#Configuration
#EnableWebMvc
#Import({RootTestConfig.class, WebCommonSecurityConfig.class})
public class WebTestConfig extends WebMvcConfigurerAdapter{
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ServletWebArgumentResolverAdapter(new DeviceWebArgumentResolver()));
argumentResolvers.add(new SitePreferenceHandlerMethodArgumentResolver());
}
}
and Test Class
public class AuthenticationControllerTest extends WebTestConfigAware {
#Test
public void testAuthenticationRequest() throws Exception {
AuthenticationRequestDto authentication = new AuthenticationRequestDto();
authentication.setUsername("admin");
authentication.setPassword("Test1234");
String jsonAuthentication = TestUtil.convertObjectToJsonString(authentication);
ResultActions res = mockMvc.perform(post("/auth")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(jsonAuthentication));
res.andExpect(status().isOk());
}

In your test class you are improperly constructing your request
// a couple of issues here explained below
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
post("/auth", authentication, device) authentication and device are interpreted as path URI so they are not needed here, your controller URI does not have any path URI variables.
If your intent is to pass 2 objects as the body of the request then you need to modify your test request and your controller request handler. You cannot pass 2 objects as the body of a request, you need to encapsulate both objects in one like
class AuthenticationRequest {
private AuthenticationRequestDto authenticationDto;
private Device device;
// constructor, getters and setters
}
In your controller
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequest request) throws AuthenticationException {
AuthenticationRequestDto authenticationDto = request.getAuthenticationDto();
Device device = request.getDevice();
// ....
}
Also in you test you need to pass a JSON object string, you are converting it to bytes (this is why you are getting a 415):
// note the change in the TestUtils, the method being called is convertObjectToJsonString (you'll need to add it)
ResultActions res = mockMvc.perform(post("/auth").contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonString(new Authenticationrequest(authentication, device))));

Related

Re-direct requests to SideEffect Utility Classes

for a spring boot application that needs to be tested below is my query.
#CustomLog
#RestController
#RequestMapping("/my_path")
public class MyController {
#GetMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JsonNode> fetchData(HttpServletRequest request){
... some code.....which also calls external apis.....
}
#PostMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
public ResponseEntity<Map<String, Object>> createMOI(HttpServletRequest request){
... some code.....which also calls external apis.....
}
}
My application calls an external service which now needs to be mocked.
this.webClient = WebClient.builder().baseUrl("http://localhost:9600/external_host_path")
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<Pojo>responseMo = webClient.post().uri("/aGivenSubPath")
.accept(MediaType.APPLICATION_JSON).bodyValue(requestPoJo)
.retrieve().bodyToMono(Pojo.class).block();
I am calling my controller API with MVC as part of springtest
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
What I am looking for is to somehow proxy or side effect
http://localhost:9600/external_host_path
and redirect all calls made for this host to a custom Utility class which provides response based on the request params to the external host programatically.
I have seen multiple examples for mockito, wireMock, mockwebserver, mockserver etc
But most of them work on a given(static path)-when(static path called)-then(give static response).
I have many calls through out the flow and I already have the logic of the utility class to generate responses for provided request arguments.
Although I was not able to find a answer to redirect webserver request to sideEffect class,
For now atleast managing by Mockito's MockBean and Answer.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#MockBean
MyExternalServiceClient serviceClient;
#Autowired
MySideEffectService sideEffect;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
Mockito.when(serviceClient.getMethod(any(),anyBoolean())).thenAnswer((Answer) invocation -> {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return sideEffect.getMethod((Map<String, List<String>>) args[0], (Boolean) args[1]);
});
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
Will still have a lookout for a way (Maybe TestContainers with image creation on the fly that will create a server with my mockCode, so that i can use hostname of this one and replace with existing real hostname)

Spring boot REST: cannot test validation of #RequestParam

I want to test controller of Spring boot API with class structure as below:
Controller:
#RestController
#RequestMapping("/member-management")
#Validated
public class MemberManagementController {
private final MemberManagementService memberManagementService;
public MemberManagementController(MemberManagementService memberManagementService) {
this.memberManagementService = memberManagementService;
}
#GetMapping(value = "/view-member")
public ResponseEntity<?> viewMember(
#NotBlank(message = "username must not be blank!!")
#Size(max = 20, message = "maximum size of username id is 20!!")
#RequestParam("username") String username) {
...
}
...
Controller advice:
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstaintViolatoinException(final ConstraintViolationException ex) {
List<String> details = new ArrayList<>();
Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
details.add(violation.getMessage());
}
ApiErrorResUtil error = new ApiErrorResUtil(String.valueOf(HttpStatus.BAD_REQUEST.value()),
"Request param error", details);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
...
}
Unit test:
#RunWith(MockitoJUnitRunner.class)
public class MemberManagementControllerTest {
#InjectMocks
private MemberManagementController memberManagementController;
#Mock
private MemberManagementService memberManagementService;
private MockMvc mockMvc;
#Before // Execute before each test method
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(memberManagementController)
.setControllerAdvice(new CustomRestExceptionHandler()) // add ControllerAdvice to controller test
.build();
}
#Test
public void viewMember_usernameSizeExceedsMaximumLimit() throws Exception {
// Value from client
String username = "a12345678901234567890"; // 21 characters
MemberResDtoDataDummy memberResDtoDataDummy = new MemberResDtoDataDummy();
when(memberManagementService.viewMember(username)).thenReturn(memberResDtoDataDummy.getMember1());
mockMvc.perform(get("/member-management/view-member").param("username", username))
.andExpect(status().isBadRequest()).andReturn();
}
Problem:
java.lang.AssertionError: Status expected:<400> but was:<200>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
...
Could anybody help me to resolve this proplem, why expected status is 200 instead of 400, other tests of POST, PUT, PATCH, DELETE request method with invalid inputted param are still working fine :(
When you want to test your UI layer without the cost of starting a server, you have to define this test as a spring-boot one and autowired the MockMvc.
#SpringBootTest
#AutoConfigureMockMvc
This class-annotations will load all the applicationContext without server.
If you just want to load your web layer, put just this annotation on your test class.
#WebMvcTest
With this annotation Spring Boot instantiates only the web layer rather than the whole context.
In both case you have to autowired the MockMvc type.

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

How to test spring-security-oauth2 resource server security?

Following the release of Spring Security 4 and it's improved support for testing I've wanted to update my current Spring security oauth2 resource server tests.
At present I have a helper class that sets up a OAuth2RestTemplate using ResourceOwnerPasswordResourceDetails with a test ClientId connecting to an actual AccessTokenUri to requests a valid token for my tests. This resttemplate is then used to make requests in my #WebIntegrationTests.
I'd like to drop the dependency on the actual AuthorizationServer, and the use of valid (if limited) user credentials in my tests, by taking advantage of the new testing support in Spring Security 4.
Up to now all my attempts at using #WithMockUser, #WithSecurityContext, SecurityMockMvcConfigurers.springSecurity() & SecurityMockMvcRequestPostProcessors.* have failed to make authenticated calls through MockMvc, and I can not find any such working examples in the Spring example projects.
Can anyone help me test my oauth2 resource server with some kind of mocked credentials, while still testing the security restrictions imposed?
** EDIT **
Sample code available here: https://github.com/timtebeek/resource-server-testing
For each of the test classes I understand why it won't work as it, but I'm looking for ways that would allow me to test the security setup easily.
I'm now thinking of creating a very permissive OAuthServer under src/test/java, which might help a bit. Does anyone have any other suggestions?
To test resource server security effectively, both with MockMvc and a RestTemplate it helps to configure an AuthorizationServer under src/test/java:
AuthorizationServer
#Configuration
#EnableAuthorizationServer
#SuppressWarnings("static-method")
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Bean
public JwtAccessTokenConverter accessTokenConverter() throws Exception {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(SecurityConfig.key("rsa"));
jwt.setVerifierKey(SecurityConfig.key("rsa.pub"));
jwt.afterPropertiesSet();
return jwt;
}
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("myclientwith")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes("myscope")
.and()
.withClient("myclientwithout")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes(UUID.randomUUID().toString());
}
}
Integration test
For integration tests one can then simply use built in OAuth2 test support rule and annotions:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApp.class)
#WebIntegrationTest(randomPort = true)
#OAuth2ContextConfiguration(MyDetails.class)
public class MyControllerIT implements RestTemplateHolder {
#Value("http://localhost:${local.server.port}")
#Getter
String host;
#Getter
#Setter
RestOperations restTemplate = new TestRestTemplate();
#Rule
public OAuth2ContextSetup context = OAuth2ContextSetup.standard(this);
#Test
public void testHelloOAuth2WithRole() {
ResponseEntity<String> entity = getRestTemplate().getForEntity(host + "/hello", String.class);
assertTrue(entity.getStatusCode().is2xxSuccessful());
}
}
class MyDetails extends ResourceOwnerPasswordResourceDetails {
public MyDetails(final Object obj) {
MyControllerIT it = (MyControllerIT) obj;
setAccessTokenUri(it.getHost() + "/oauth/token");
setClientId("myclientwith");
setUsername("user");
setPassword("password");
}
}
MockMvc test
Testing with MockMvc is also possible, but needs a little helper class to get a RequestPostProcessor that sets the Authorization: Bearer <token> header on requests:
#Component
public class OAuthHelper {
// For use with MockMvc
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
#Autowired
ClientDetailsService clientDetailsService;
#Autowired
AuthorizationServerTokenServices tokenservice;
OAuth2AccessToken createAccessToken(final String clientId) {
// Look up authorities, resourceIds and scopes based on clientId
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
// Default values for other parameters
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
// Create request
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes,
resourceIds, redirectUrl, responseTypes, extensionProperties);
// Create OAuth2AccessToken
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
}
Your MockMvc tests must then get a RequestPostProcessor from the OauthHelper class and pass it when making requests:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApp.class)
#WebAppConfiguration
public class MyControllerTest {
#Autowired
private WebApplicationContext webapp;
private MockMvc mvc;
#Before
public void before() {
mvc = MockMvcBuilders.webAppContextSetup(webapp)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
#Autowired
private OAuthHelper helper;
#Test
public void testHelloWithRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwith");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isOk());
}
#Test
public void testHelloWithoutRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwithout");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isForbidden());
}
}
A full sample project is available on GitHub:
https://github.com/timtebeek/resource-server-testing
I found a much easier way to do this following directions I read here: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext. This solution is specific to testing #PreAuthorize with #oauth2.hasScope but I'm sure it could be adapted for other situations as well.
I create an annotation which can be applied to #Tests:
WithMockOAuth2Scope
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public #interface WithMockOAuth2Scope {
String scope() default "";
}
WithMockOAuth2ScopeSecurityContextFactory
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import java.util.HashSet;
import java.util.Set;
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
#Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Set<String> scope = new HashSet<>();
scope.add(mockOAuth2Scope.scope());
OAuth2Request request = new OAuth2Request(null, null, null, true, scope, null, null, null, null);
Authentication auth = new OAuth2Authentication(request, null);
context.setAuthentication(auth);
return context;
}
}
Example test using MockMvc:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class LoadScheduleControllerTest {
private MockMvc mockMvc;
#Autowired
LoadScheduleController loadScheduleController;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(loadScheduleController)
.build();
}
#Test
#WithMockOAuth2Scope(scope = "dataLicense")
public void testSchedule() throws Exception {
mockMvc.perform(post("/schedule").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andDo(print());
}
}
And this is the controller under test:
#RequestMapping(value = "/schedule", method = RequestMethod.POST)
#PreAuthorize("#oauth2.hasScope('dataLicense')")
public int schedule() {
return 0;
}
Spring Boot 1.5 introduced test slices like #WebMvcTest. Using these test slices and manually load the OAuth2AutoConfiguration gives your tests less boilerplate and they'll run faster than the proposed #SpringBootTest based solutions. If you also import your production security configuration, you can test that the configured filter chains is working for your web services.
Here's the setup along with some additional classes that you'll probably find beneficial:
Controller:
#RestController
#RequestMapping(BookingController.API_URL)
public class BookingController {
public static final String API_URL = "/v1/booking";
#Autowired
private BookingRepository bookingRepository;
#PreAuthorize("#oauth2.hasScope('myapi:write')")
#PatchMapping(consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
public Booking patchBooking(OAuth2Authentication authentication, #RequestBody #Valid Booking booking) {
String subjectId = MyOAuth2Helper.subjectId(authentication);
booking.setSubjectId(subjectId);
return bookingRepository.save(booking);
}
}
Test:
#RunWith(SpringRunner.class)
#AutoConfigureJsonTesters
#WebMvcTest
#Import(DefaultTestConfiguration.class)
public class BookingControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private JacksonTester<Booking> json;
#MockBean
private BookingRepository bookingRepository;
#MockBean
public ResourceServerTokenServices resourceServerTokenServices;
#Before
public void setUp() throws Exception {
// Stub the remote call that loads the authentication object
when(resourceServerTokenServices.loadAuthentication(anyString())).thenAnswer(invocation -> SecurityContextHolder.getContext().getAuthentication());
}
#Test
#WithOAuthSubject(scopes = {"myapi:read", "myapi:write"})
public void mustHaveValidBookingForPatch() throws Exception {
mvc.perform(patch(API_URL)
.header(AUTHORIZATION, "Bearer foo")
.content(json.write(new Booking("myguid", "aes")).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is2xxSuccessful());
}
}
DefaultTestConfiguration:
#TestConfiguration
#Import({MySecurityConfig.class, OAuth2AutoConfiguration.class})
public class DefaultTestConfiguration {
}
MySecurityConfig (this is for production):
#Configuration
#EnableOAuth2Client
#EnableResourceServer
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/v1/**").authenticated();
}
}
Custom annotation for injecting scopes from tests:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithOAuthSubjectSecurityContextFactory.class)
public #interface WithOAuthSubject {
String[] scopes() default {"myapi:write", "myapi:read"};
String subjectId() default "a1de7cc9-1b3a-4ecd-96fa-dab6059ccf6f";
}
Factory class for handling the custom annotation:
public class WithOAuthSubjectSecurityContextFactory implements WithSecurityContextFactory<WithOAuthSubject> {
private DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
#Override
public SecurityContext createSecurityContext(WithOAuthSubject withOAuthSubject) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
// Copy of response from https://myidentityserver.com/identity/connect/accesstokenvalidation
Map<String, ?> remoteToken = ImmutableMap.<String, Object>builder()
.put("iss", "https://myfakeidentity.example.com/identity")
.put("aud", "oauth2-resource")
.put("exp", OffsetDateTime.now().plusDays(1L).toEpochSecond() + "")
.put("nbf", OffsetDateTime.now().plusDays(1L).toEpochSecond() + "")
.put("client_id", "my-client-id")
.put("scope", Arrays.asList(withOAuthSubject.scopes()))
.put("sub", withOAuthSubject.subjectId())
.put("auth_time", OffsetDateTime.now().toEpochSecond() + "")
.put("idp", "idsrv")
.put("amr", "password")
.build();
OAuth2Authentication authentication = defaultAccessTokenConverter.extractAuthentication(remoteToken);
context.setAuthentication(authentication);
return context;
}
}
I use a copy of the response from our identity server for creating a realistic OAuth2Authentication. You can probably just copy my code. If you want to repeat the process for your identity server, place a breakpoint in org.springframework.security.oauth2.provider.token.RemoteTokenServices#loadAuthentication or org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices#extractAuthentication, depending on whether you have configured a custom ResourceServerTokenServices or not.
There is alternative approach which I believe to be cleaner and more meaningful.
The approach is to autowire the token store and then add a test token which can then be used by the rest client.
An example test:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerIT {
#Autowired
private TestRestTemplate testRestTemplate;
#Autowired
private TokenStore tokenStore;
#Before
public void setUp() {
final OAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
final ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_CLIENT");
final OAuth2Authentication authentication = new OAuth2Authentication(
new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), null);
tokenStore.storeAccessToken(token, authentication);
}
#Test
public void testGivenPathUsersWhenGettingForEntityThenStatusCodeIsOk() {
final HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer FOO");
headers.setContentType(MediaType.APPLICATION_JSON);
// Given Path Users
final UriComponentsBuilder uri = UriComponentsBuilder.fromPath("/api/users");
// When Getting For Entity
final ResponseEntity<String> response = testRestTemplate.exchange(uri.build().toUri(), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
// Then Status Code Is Ok
assertThat(response.getStatusCode(), is(HttpStatus.OK));
}
}
Personally I believe that it is not appropriate to unit test a controller with security enabled since security is a separate layer to the controller. I would create an integration test that tests all of the layers together. However the above approach can easily be modified to create a Unit Test with that uses MockMvc.
The above code is inspired by a Spring Security test written by Dave Syer.
Note this approach is for resource servers that share the same token store as the authorisation server. If your resource server does not share the same token store as the authorisation server I recommend using wiremock to mock the http responses.
I have another solution for this. See below:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
#ActiveProfiles("test")
public class AccountContollerTest {
public static Logger log = LoggerFactory.getLogger(AccountContollerTest.class);
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private UserRepository users;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private CustomClientDetailsService clientDetialsService;
#Before
public void setUp() {
mvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity(springSecurityFilterChain))
.build();
BaseClientDetails testClient = new ClientBuilder("testclient")
.secret("testclientsecret")
.authorizedGrantTypes("password")
.scopes("read", "write")
.autoApprove(true)
.build();
clientDetialsService.addClient(testClient);
User user = createDefaultUser("testuser", passwordEncoder.encode("testpassword"), "max", "Mustermann", new Email("myemail#test.de"));
users.deleteAll();
users.save(user);
}
#Test
public void shouldRetriveAccountDetailsWithValidAccessToken() throws Exception {
mvc.perform(get("/api/me")
.header("Authorization", "Bearer " + validAccessToken())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.userAuthentication.name").value("testuser"))
.andExpect(jsonPath("$.authorities[0].authority").value("ROLE_USER"));
}
#Test
public void shouldReciveHTTPStatusUnauthenticatedWithoutAuthorizationHeader() throws Exception{
mvc.perform(get("/api/me")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isUnauthorized());
}
private String validAccessToken() throws Exception {
String username = "testuser";
String password = "testpassword";
MockHttpServletResponse response = mvc
.perform(post("/oauth/token")
.header("Authorization", "Basic "
+ new String(Base64Utils.encode(("testclient:testclientsecret")
.getBytes())))
.param("username", username)
.param("password", password)
.param("grant_type", "password"))
.andDo(print())
.andReturn().getResponse();
return new ObjectMapper()
.readValue(response.getContentAsByteArray(), OAuthToken.class)
.accessToken;
}
#JsonIgnoreProperties(ignoreUnknown = true)
private static class OAuthToken {
#JsonProperty("access_token")
public String accessToken;
}
}
Hope it will help!
OK, I've not yet been able to test my standalone oauth2 JWT token protected resource-server using the new #WithMockUser or related annotations.
As a workaround, I have been able to integration test my resource server security by setting up a permissive AuthorizationServer under src/test/java, and having that define two clients I use through a helper class. This gets me some of the way there, but it's not yet as easy as I'd like to test various users, roles, scopes, etc.
I'm guessing from here on it should be easier to implement my own WithSecurityContextFactory that creates an OAuth2Authentication, instead of the usual UsernamePasswordAuthentication. However, I have not yet been able to work out the detail of how to easily set this up. Any comments or suggestions how to set this up are welcome.
I found an easy and rapid way for testing spring security resource server with any token store. Im my example #EnabledResourceServeruses jwt token store.
The magic here is I replaced JwtTokenStore with InMemoryTokenStore at integration test.
#RunWith (SpringRunner.class)
#SpringBootTest (classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles ("test")
#TestPropertySource (locations = "classpath:application.yml")
#Transactional
public class ResourceServerIntegrationTest {
#Autowired
private TokenStore tokenStore;
#Autowired
private ObjectMapper jacksonObjectMapper;
#LocalServerPort
int port;
#Configuration
protected static class PrepareTokenStore {
#Bean
#Primary
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
private OAuth2AccessToken token;
private OAuth2Authentication authentication;
#Before
public void init() {
RestAssured.port = port;
token = new DefaultOAuth2AccessToken("FOO");
ClientDetails client = new BaseClientDetails("client", null, "read", "client_credentials", "ROLE_READER,ROLE_CLIENT");
// Authorities
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_READER"));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("writer", "writer", authorities);
authentication = new OAuth2Authentication(new TokenRequest(null, "client", null, "client_credentials").createOAuth2Request(client), authenticationToken);
tokenStore.storeAccessToken(token, authentication);
}
#Test
public void gbsUserController_findById() throws Exception {
RestAssured.given().log().all().when().headers("Authorization", "Bearer FOO").get("/gbsusers/{id}", 2L).then().log().all().statusCode(HttpStatus.OK.value());
}
One more solution I tried to detail enough :-D
It is based on setting an Authorization header, like some above, but I wanted:
Not to create actually valid JWT tokens and using all JWT authentication stack (unit tests...)
Test authentication to contain test-case defined scopes and authorities
So I've:
created custom annotations to set up a per-test OAuth2Authentication: #WithMockOAuth2Client (direct client connection) & #WithMockOAuth2User (client acting on behalf of an end user => includes both my custom #WithMockOAuth2Client and Spring #WithMockUser)
#MockBean the TokenStore to return the OAuth2Authentication configured with above custom annotations
provide MockHttpServletRequestBuilder factories that set a specific Authorization header intercepted by TokenStore mock to inject expected authentication.
The result to get you tested:
#WebMvcTest(MyController.class) // Controller to unit-test
#Import(WebSecurityConfig.class) // your class extending WebSecurityConfigurerAdapter
public class MyControllerTest extends OAuth2ControllerTest {
#Test
public void testWithUnauthenticatedClient() throws Exception {
api.post(payload, "/endpoint")
.andExpect(...);
}
#Test
#WithMockOAuth2Client
public void testWithDefaultClient() throws Exception {
api.get("/endpoint")
.andExpect(...);
}
#Test
#WithMockOAuth2User
public void testWithDefaultClientOnBehalfDefaultUser() throws Exception {
MockHttpServletRequestBuilder req = api.postRequestBuilder(null, "/uaa/refresh")
.header("refresh_token", JWT_REFRESH_TOKEN);
api.perform(req)
.andExpect(status().isOk())
.andExpect(...)
}
#Test
#WithMockOAuth2User(
client = #WithMockOAuth2Client(
clientId = "custom-client",
scope = {"custom-scope", "other-scope"},
authorities = {"custom-authority", "ROLE_CUSTOM_CLIENT"}),
user = #WithMockUser(
username = "custom-username",
authorities = {"custom-user-authority"}))
public void testWithCustomClientOnBehalfCustomUser() throws Exception {
api.get(MediaType.APPLICATION_ATOM_XML, "/endpoint")
.andExpect(status().isOk())
.andExpect(xpath(...));
}
}
I've tried many ways. But my solution is easier than others. I'm using OAuth2 JWT authentication in my spring boot application. My goal is to do a contract test. I'm writing a script with groovy and the contract plugin generates test codes for me. Therefore, I cannot interfere with the codes. I have a simple BaseTest class. I need to do all the necessary configurations in this class. This solution worked for me.
Imported dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>
Imported Plugins:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<extensions>true</extensions>
<configuration>
<baseClassForTests>com.test.services.BaseTestClass
</baseClassForTests>
</configuration>
</plugin>
BaseTestClass.java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#DirtiesContext
#AutoConfigureMessageVerifier
#ContextConfiguration
#WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
public class BaseTestClass {
#Autowired
private MyController myController;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(myController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
}
myFirstScenario.groovy (package:"/test/resources/contracts"):
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return ok"
request {
method GET()
url("/api/contract/test") {
headers {
header("Authorization","Bearer FOO")
}
}
}
response {
status 200
}
}
MyController.java:
#RestController
#RequestMapping(value = "/api/contract")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public class MyController {
...
}
if you want to test for non-admin users you can use:
#WithMockUser(username = "admin", roles = {"USER"})

Testing Spring #MVC annotations

I ran into a problem the other day where a #Valid annotation was accidentally removed from a controller class. Unfortunately, it didn't break any of our tests. None of our unit tests actually exercise the Spring AnnotationMethodHandlerAdapter pathway. We just test our controller classes directly.
How can I write a unit or integration test that will correctly fail if my #MVC annotations are wrong? Is there a way I can ask Spring to find and exercise the relevant controller with a MockHttpServlet or something?
I write integration tests for this kind of thing. Say you have a bean with validation annotations:
public class MyForm {
#NotNull
private Long myNumber;
...
}
and a controller that handles the submission
#Controller
#RequestMapping("/simple-form")
public class MyController {
private final static String FORM_VIEW = null;
#RequestMapping(method = RequestMethod.POST)
public String processFormSubmission(#Valid MyForm myForm,
BindingResult result) {
if (result.hasErrors()) {
return FORM_VIEW;
}
// process the form
return "success-view";
}
}
and you want to test that the #Valid and #NotNull annotations are wired correctly:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml"})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
}
ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
final ModelAndView mav = handlerAdapter.handle(request, response, controller);
return mav;
}
#Test
public void testProcessFormSubmission() throws Exception {
request.setMethod("POST");
request.setRequestURI("/simple-form");
request.setParameter("myNumber", "");
final ModelAndView mav = handle(request, response);
// test we're returned back to the form
assertViewName(mav, "simple-form");
// make assertions on the errors
final BindingResult errors = assertAndReturnModelAttributeOfType(mav,
"org.springframework.validation.BindingResult.myForm",
BindingResult.class);
assertEquals(1, errors.getErrorCount());
assertEquals("", errors.getFieldValue("myNumber"));
}
See my blog post on integration testing Spring's MVC annotations
Sure. There's no reason why your test can't instantiate its own DispatcherServlet, inject it with the various items which it would have in a container (e.g. ServletContext), including the location of the context definition file.
Spring comes with a variety of servlet-related MockXYZ classes for this purpose, including MockServletContext, MockHttpServletRequest and MockHttpServletResponse. They're not really "mock" objects in the usual sense, they're more like dumb stubs, but they do the job.
The servlet's test context would have the usual MVC-related beans, plus your beans to test. Once the servlet is initialized, create the mock requests and responses, and feed them into the servet's service() method. If request gets routed correctly, you can check the results as written to the mock response.
In upcoming spring 3.2 (SNAPSHOT available) or with spring-test-mvc (https://github.com/SpringSource/spring-test-mvc) you can do it like this:
first we emulate Validation as we do not want to test the validator, just want to know if validation is called.
public class LocalValidatorFactoryBeanMock extends LocalValidatorFactoryBean
{
private boolean fakeErrors;
public void fakeErrors ( )
{
this.fakeErrors = true;
}
#Override
public boolean supports ( Class<?> clazz )
{
return true;
}
#Override
public void validate ( Object target, Errors errors, Object... validationHints )
{
if (fakeErrors)
{
errors.reject("error");
}
}
}
this is our test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class RegisterControllerTest
{
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Autowired
#InjectMocks
private RegisterController registerController;
#Autowired
private LocalValidatorFactoryBeanMock validator;
#Before
public void setup ( )
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
// if you want to inject mocks into your controller
MockitoAnnotations.initMocks(this);
}
#Test
public void testPostValidationError ( ) throws Exception
{
validator.fakeErrors();
MockHttpServletRequestBuilder post = post("/info/register");
post.param("name", "Bob");
ResultActions result = getMockMvc().perform(post);
// no redirect as we have errors
result.andExpect(view().name("info/register"));
}
#Configuration
#Import(DispatcherServletConfig.class)
static class Config extends WebMvcConfigurerAdapter
{
#Override
public Validator getValidator ( )
{
return new LocalValidatorFactoryBeanMock();
}
#Bean
RegisterController registerController ( )
{
return new RegisterController();
}
}
}

Categories

Resources