I am trying to write a Unit tests for all of my service classes, but I cannot find a solution on how to mock a #PreAuthorize above my controller methods. As an example:
I have this function in controller:
#GetMapping("/users")
#PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<List<User>> getUsers() {
return service.getUsers();
}
And this in my service class:
public ResponseEntity<List<User>> getUsers() {
return new ResponseEntity<>(userRepository.findAll(), HttpStatus.OK);
}
WebSecurity class:
protected void configure(HttpSecurity http) throws Exception {
http = http.cors().and().csrf().disable();
http = http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and();
http = http
.exceptionHandling()
.authenticationEntryPoint(
(request, response, ex) -> response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
ex.getMessage()
)
)
.and();
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/").permitAll()
.anyRequest().authenticated();
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
}
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt ->
Optional.ofNullable(jwt.getClaimAsStringList("permissions"))
.stream()
.flatMap(Collection::stream)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList())
);
return converter;
}
Now I am trying to write a unit test:
#SpringBootTest
#AutoConfigureMockMvc
public class UserControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private UserService userService;
#Test
#WithMockJwtAuth(claims = #OpenIdClaims(otherClaims
= #Claims(stringClaims = #StringClaim(name = "permissions", value = "{ADMIN}"))))
public void getAllUsers_shouldBeSuccess() throws Exception {
ArrayList<User> users = new ArrayList<>();
users.add(new User("0", true, new Role("USER")));
when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK));
mvc.perform(get("/users"))
.andExpect(status().isOk());
}
}
But I receive an error on mvc.perform call:
java.lang.NoClassDefFoundError: `org/springframework/security/web/context/SecurityContextHolderFilter`
UPDATE:
I've tried https://github.com/ch4mpy/spring-addons and added #WithMockBearerTokenAuthentication, but I still receive the same error. Also to note: if I removed all # and left only with #Test above the method, I receive 401 error.
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(otherClaims
= #Claims(stringClaims = #StringClaim(name = "permissions", value = "{ADMIN}"))))
public void getAllUsers_shouldBeSuccess() throws Exception {
ArrayList<User> users = new ArrayList<>();
users.add(new User("0", true, new Role("USER")));
when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK));
mvc.perform(get("/users"))
.andExpect(status().isOk());
}
Sadly, spring-security team chose to include in test framework MockMvc request post-processors and WebTestClient request mutators only, which limits OAuth2 authentication mocking to controllers unit-tests.
Hopefully, I kept my work on test annotations in a set of libs I publish on maven-central: https://github.com/ch4mpy/spring-addons. You can test any #Component with it (sample adapted from here):
//---------------------------------------------------//
// Test secured #Component which isn't a #Controller //
//---------------------------------------------------//
// Import web-security configuration and tested component class
#Import({ SampleApi.WebSecurityConfig.class, MessageService.class })
#ExtendWith(SpringExtension.class)
class MessageServiceTests {
// Auto-wire tested component
#Autowired
private MessageService messageService;
// Mock tested component dependencies
#MockBean
GreetingRepo greetingRepo;
#BeforeEach
public void setUp() {
when(greetingRepo.findUserSecret("ch4mpy")).thenReturn("Don't tel it");
}
#Test()
void greetWitoutAuthentication() {
// Call tested component methods directly (don't use MockMvc nor WebTestClient)
assertThrows(Exception.class, () -> messageService.getSecret("ch4mpy"));
}
#Test
#WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = #OpenIdClaims(preferredUsername = "ch4mpy"))
void greetWithMockJwtAuth() {
assertThat(messageService.getSecret("ch4mpy")).isEqualTo("Don't tel it");
}
}
Edit for updated question
Only #Controller unit-tests should run within the context of an HTTP request.
This means that #WebMvcTest (or #WebFluxTest) and MockMvc (or WebTestClient) must be used in #Controller tests only.
Unit-tests for any other type of #Component (#Service or #Repository for instance) should be written without the context of a request. This means that none of #WebMvcTest, #WebFluxTest, MockMvcand WebTestClient should be used in such tests.
The sample above shows how to structure such tests:
Trigger spring-context configuration
#Import web-security configuration and your #Component class
provide #MockBean for all of your #Component dependencies
in the test, call tested component methods directly (do not try to create a mockMvc request)
Edit for the 2nd question modification
I am trying to write a Unit tests for all of my service classes
Apparently, this first statement is not your main concern anymore as you're trying to write integration-tests for #Controller along with the #Services, #Repositories and other #Components it is injected. This is actually a completely different question than unit-testing each of those separately (mocking others).
NoClassDefFoundError on SecurityContextHolderFilter means that spring-security-web is not on your classpath. It should be, even during the tests. Check your dependencies (pom or gradle file that you did not include in your question)
Please also note that:
you might want to write value = "ADMIN" instead of value = "{ADMIN}" (unless you really want curly-braces in your authority name)
you can use just #WithMockJwtAuth("ADMIN") instead of #WithMockJwtAuth(claims = #OpenIdClaims(otherClaims = #Claims(stringClaims = #StringClaim(name = "permissions", value = "ADMIN"))))
you are allowed to read docs (you and I would save quite some time). This includes Spring doc and mine: home one and more importantly tutorials
Related
I have a main app that exposes endpoint like this. I have a test like this. It simply uses TestRestTemplate, calls actuator endpoint and checks health:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#TestPropertySource(locations = "classpath:application-test.properties")
public class HealthDataImporterApplicationTest {
#Autowired
private TestRestTemplate testRestTemplate;
#LocalServerPort
private int serverPort;
#Test
public void health() {
assertThat("status", read(get("health"), "$.status").equals("UP"));
}
private String get(final String resource) {
return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource),
String.class);
}
}
I now added SecurityConfig class to add authentication to the app. Here is SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private static final String ACTUATOR_URL = "/actuator/**";
private static final String SCOPE_PREFIX = "SCOPE_";
private static final String PERMISSION_SCOPE = "scope:scope";
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, ACTUATOR_URL).permitAll()
.anyRequest().hasAuthority(SCOPE_PREFIX + PERMISSION_SCOPE)
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
}
Main app imports this SecurityConfig class so authentication is there. However, it fails the existing test because it complains it is missing auth0.audience and other value. It seems like it is trying to actually call auth0 url, which I don't want the test to. Otherwise, I have to put actual auth0 audience and domain which is sensitive data.
How can I disable spring security with TestRestTemplate? Is it worth to test this class at all?
As you are using spring-boot, do not override the JwtDecoder just to check audiences. Use spring.security.oauth2.resourceserver.jwt.audiences property instead (this is a comma separated list of acceptable audience).
Rather than deactivating security, I prefer to use mocked identities and include access-control in test coverage, using MockMvc (yes, even in #SpringBootTest with many components wired).
Refer to this other answer for details on how to do that in unit-test and integration-test.
Because I find request post-processors not that readable and I happen to unit-test secured components which are not controllers (like #Service or #Respository with method-security like #PreAuthorize, #PostFilter, etc.), I created a lib with test annotations for OAuth2:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.get("/actuator/health")
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_openid", "SCOPE_scope:scope")
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isForbidden());
}
}
As comparison, the same sample with just spring-security-test:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.perform(get("/actuator/health/liveness"))
.andExpect(status().isOk());
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"),
new SimpleGrantedAuthority("SCOPE_scope:scope"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
In JHipster, we generate a TestSecurityConfiguration class that mocks the JwtDecoder bean so no identity provider is needed. You can see the code here. I've copied it below for your convenience.
package com.auth0.flickr2.config;
import static org.mockito.Mockito.mock;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
/**
* This class allows you to run unit and integration tests without an IdP.
*/
#TestConfiguration
#Import(OAuth2Configuration.class)
public class TestSecurityConfiguration {
#Bean
ClientRegistration clientRegistration() {
return clientRegistrationBuilder().build();
}
#Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration.Builder clientRegistrationBuilder() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("end_session_endpoint", "https://jhipster.org/logout");
return ClientRegistration
.withRegistrationId("oidc")
.issuerUri("{baseUrl}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("read:user")
.authorizationUri("https://jhipster.org/login/oauth/authorize")
.tokenUri("https://jhipster.org/login/oauth/access_token")
.jwkSetUri("https://jhipster.org/oauth/jwk")
.userInfoUri("https://api.jhipster.org/user")
.providerConfigurationMetadata(metadata)
.userNameAttributeName("id")
.clientName("Client Name")
.clientId("client-id")
.clientSecret("client-secret");
}
#Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
#Bean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
}
This class is then referenced in the test and overrides the SecurityConfig.
#SpringBootTest(classes = { Flickr2App.class, TestSecurityConfiguration.class })
The full project repo is at https://github.com/oktadev/auth0-full-stack-java-example.
I have a spring-boot Webflux application and I am writing some tests using Spock and Groovy.
My Controllers are secured with OAuth opaque token which I need to mock a response from introspection.
My test properties are:
spring.security.oauth2.resourceserver.opaquetoken.client-id=fake_client
spring.security.oauth2.resourceserver.opaquetoken.client-secret=fake_secret
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:8089/api/v1/oauth/token/introspect
My test uses WebClient as below:
webClient.post()
.uri(URL.toString()))
.accept(MediaType.APPLICATION_JSON)
.headers(http -> http.setBearerAuth("bearer_token"))
.exchange()
.expectStatus()
.is2xxSuccessful()
I found the solution.
You have to configure WireMockServer and then stub the response. Working solution below:
#ContextConfiguration
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SomeTest extends Specification {
private static final int PORT = 8089;
private WireMockServer wireMockServer;
def setup() {
wireMockServer = new WireMockServer(options().port(PORT))
wireMockServer.start()
WireMock.configureFor("localhost", wireMockServer.port())
def stubIntrospection() {
stubFor(post("/api/v1/oauth/token/introspect")
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBodyFile("path-to-my-file.json")))
}
def "my test case" () {
stubIntrospection()
//my test here
}
}
There is no difference between Spock and JUnit when mocking spring security-context. You can provide with mocked Authentication for both unit (#WebFluxTest) and integration (#SpringBootTest) with either:
org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOpaqueToken mutator for WebTestClient
#WithMockBearerTokenAuthentication annotation.
Mocked BearerTokenAuthentication, Spring's auth implementation for token introspection (unless you explicitly changed it in your conf), is put in test security-context directly by annotation or mutator: authorization header is not introspected (it is completely ignored and can be omitted).
Sample (with JUnit, just adapt to your test framework) in this project:
#Test
void securedRouteWithAuthorizedPersonnelViaMutatorIsOk() throws Exception {
api
.mutateWith(mockOpaqueToken()
.attributes(attributes -> attributes.put(StandardClaimNames.PREFERRED_USERNAME, "Ch4mpy"))
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
.get().uri("https://localhost/secured-route")
.exchange()
.expectStatus().isOk();
}
#Test
#WithMockBearerTokenAuthentication(
authorities = "ROLE_AUTHORIZED_PERSONNEL",
attributes = #OpenIdClaims(preferredUsername = "Ch4mpy"))
void securedRouteWithAuthorizedPersonnelViaAnnotationIsOk() throws Exception {
api
.get().uri("https://localhost/secured-route")
.exchange()
.expectStatus().isOk();
}
When testing other secured #Component type than #Controller (like #Service or #Repository), only test annotation is usable as there is no HTTP request:
#Import({ SecurityConfig.class, SecretRepo.class })
class SecretRepoTest {
// auto-wire tested component
#Autowired
SecretRepo secretRepo;
#Test
void whenNotAuthenticatedThenThrows() {
// call tested components methods directly (do not use MockMvc nor
// WebTestClient)
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block());
}
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenAuthenticatedAsSomeoneElseThenThrows() {
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy").block());
}
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(preferredUsername = "ch4mpy"))
void whenAuthenticatedWithSameUsernameThenReturns() {
assertEquals("Don't ever tell it", secretRepo.findSecretByUsername("ch4mpy").block());
}
}
Annotation is available from
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<scope>test</scope>
</dependency>
Which is a transient dependency of
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webflux-introspecting-test</artifactId>
<scope>test</scope>
</dependency>
I used the later in sample to also have WebTestClientSupport, but first is enough for just test annotations.
I've got a REST controller I'm trying to test, but when attempting to POST to it, I get a 404. The test is JUnit 5 and Spring Boot 2.1.5. If I run the application, I can hit the controller via Postman. I've run it in debug mode and verified that myController is not null and has the mocked services injected into it. What am I missing here? spring-boot-starter-test is a dependency, and junit4 is an exclusion.
#RestController
#Slf4j
#RequestMapping(path = /integrations,
produces = "application/json")
public class MyController {
private MyService myService;
private MyValidationService myValidationService;
public MyController(MySerivce service, MyValidationService myValidationService) {
this.myService = service;
this.myValidationService = myValidationService;
}
#PostMapping(path = "/users", produces = "application/json", consumes =
"application/json")
public ResponseEntity<User> getUserList(#Valid #RequestBody final RequestPayload
requestPayload, #RequestHeader Map<String, String> headers) throws MyException {
// check the credentials and permission
Map<String, String> credInfo = myValidationService.validateHeaders(headers);
// process payload
return myService.retrieveUsers(requestPayload);
}
}
Test is as follows:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
class MyControllerTest {
#MockBean
private MyService myService;
#MockBean
private MyValidationService myValidationService;
#Autowired
private MockMvc mockMvc;
#Autowired
MyController myController;
#Test
public void contextLoads() throws Exception {
Assert.assertNotNull(myController);
}
#Test
void getUserList() throws Exception {
List<User> users = returnUserList();
HttpEntity<RequestPayload> requestHttpEntity = new HttpEntity<>(returnPayload(), null);
when(myService.retrieveUsers(any(RequestPayload.class))).thenReturn(users);
mockMvc.perform(post("/integrations/users")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(returnPayload()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful());
}
}
The response I get is:
MockHttpServletResponse:
Status = 404
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I appreciate the answers, but I discovered what the problem was - and it was a bonehead one!
It turns out that I included the context path in the mockMvc call when that value is already provided in the application.properties file! So instead of a URI of /integrations/users, the real URI being used was /integrations/integrations/users, which unsurprisingly does not exist!
Thanks to all and sorry for not taking my eyes in my hands and looking more closely.
You can use a slice test annotation instead. For testing a controller you can use #WebMvcTest.
Your setup would look like this:
#SpringBootTest(value = MyController.class)
#ExtendWith(SpringExtension.class)
class MyControllerTest {
You can still #Autowired the MockMvc. Also there is no need to autowire your controller in the test.
I'm using Spring Boot 1.2.5-RELEASE. I have a controller that receive a MultipartFile and a String
#RestController
#RequestMapping("file-upload")
public class MyRESTController {
#Autowired
private AService aService;
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void fileUpload(
#RequestParam(value = "file", required = true) final MultipartFile file,
#RequestParam(value = "something", required = true) final String something) {
aService.doSomethingOnDBWith(file, value);
}
}
Now, the service works well. I tested it with PostMan and eveything goes as expected.
Unfortunately, I cannot write a standalone unit test for that code. The current unit test is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
public class ControllerTest{
MockMvc mockMvc;
#Mock
AService aService;
#InjectMocks
MyRESTController controller;
#Before public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
//File is correctly loaded
final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));
doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
mockMvc.perform(
post("/file-upload")
.requestAttr("file", multipartFile.getBytes())
.requestAttr("something", ":(")
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isCreated());
}
}
Test fails with
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Now, in the MultipartAutoConfiguration class from Spring Boot I see that a MultipartResolver is auto configured. But, I guess that with the standaloneSetup of MockMvcBuilders I cannot access this.
I tried several configurations of the unit test that I don't report for brevity. Especially, I also tried rest-assured as shown here, but honestly this doesn't work because it seems that I cannot mock the AService instance.
Any solution?
You are trying to combine here unit test (standaloneSetup(controller).build();) with Spring integration test (#RunWith(SpringJUnit4ClassRunner.class)).
Do one or the other.
Integration test will need to use something like code below. The problem would be faking of beans. There are ways to fake such bean with #Primary annotation and #Profile annotation (you create testing bean which will override main production bean). I have some examples of such faking of Spring beans (e.g. this bean is replaced by this bean in this test).
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Secodn option is to remove #RunWith(SpringJUnit4ClassRunner.class) and other class level configuration on your test and test controller without Spring Context with standalone setup. That way you can't test validation annotations on your controller, but you can use Spring MVC annotations. Advantage is possibility to fake beans via Mockito (e.g. via InjectMocks and Mock annotations)
I mixed what lkrnak suggested and Mockito #Spy functionality. I use REST-Assured to do the call. So, I did as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port:0"})
public class ControllerTest{
{
System.setProperty("spring.profiles.active", "unit-test");
}
#Autowired
#Spy
AService aService;
#Autowired
#InjectMocks
MyRESTController controller;
#Value("${local.server.port}")
int port;
#Before public void setUp(){
RestAssured.port = port;
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
doNothing().when(aService)
.doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
given()
.multiPart("file", file)
.multiPart("something", ":(")
.when().post("/file-upload")
.then().(HttpStatus.CREATED.value());
}
}
the service is defined as
#Profile("unit-test")
#Primary
#Service
public class MockAService implements AService {
//empty methods implementation
}
The error says the request is not a multi-part request. In other words at that point it's expected to have been parsed. However in a MockMvc test there is no actual request. It's just mock request and response. So you'll need to use perform.fileUpload(...) in order to set up a mock file upload request.
I have set up spring boot application using Gradle. Now I do understand that #EnableAutoConnfiguration configures the application based on dependencies in a class path. I am pretty happy to avoid all of the plumbing but things start happening which I wish wouldn't.
Here are my dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE')
compile 'org.springframework.hateoas:spring-hateoas:0.17.0.RELEASE'
compile 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'com.google.guava:guava:18.0'
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
compile 'commons-beanutils:commons-beanutils:1.9.2'
runtime 'org.hsqldb:hsqldb:2.3.2'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'com.jayway.jsonpath:json-path:2.0.0'
}
My application class:
#ComponentScan("org.home.project")
#SpringBootApplication
//#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
A snippet from UserController:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<ResourceSupport> create(#Valid #RequestBody UserCreateRequest ucr, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw new InvalidRequestException("Bad Request", bindingResult);
Long userId = userService.create(ucr);
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(UserEndpoint.class).withSelfRel());
resource.add(linkTo(methodOn(UserEndpoint.class).update(userId, null, null)).withRel(VIEW_USER));
resource.add(linkTo(methodOn(UserEndpoint.class).delete(userId)).withRel(DELETE_USER));
return new ResponseEntity(resource, HttpStatus.CREATED);
}
The UserController.java has two annotations:
#RestController
#RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
First of - notice the commented out #EnableHyperdiaSupport annotation - links in the ResourceSupport instance are still serialized to hal+json format despite media type produced and media type set in the request. This happens automatically when 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE' is introduced in the dependencies. How would one go about configuring it explicitly ?
Another issue are unit tests.
This passes:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class UserControllerTest {
...ommited for brevity...
#InjectMocks
private UserController testObject;
#Before
public void setUp() throws Exception {
initMocks(this);
mockMvc = standaloneSetup(testObject).build();
}
#Test
public void testUserCreatedLinks() throws Exception {
mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content(data))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.links.[*].rel", hasItem("self")));
}
...ommited fro brevity...
}
The post request returns a standard JSON response in the test - not HAL+JSON. Is there a way to reconfigure this so that unit testing #RestController with MockServletContext would produce HAL+JSON or getting back to problem number 1 - how to configure the response format explicitly so that Jackson serializer would not produce hal+json ?
You're running your test using Spring MVC Test's standaloneSetup which uses a bare minimum of configuration to get your controller up and running. That configuration isn't the same as the configuration that will be used when you run the whole application.
If you want to use the same configuration, you could use webAppContextSetup:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class SomeTests {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
}
Alternatively, you can replicate Spring HATEOAS's configuration in the standalone setup. Note that this runs the risk of your tests' configuration deviating from your application's configuration. You'd create the MockMvc instance like this:
TypeConstrainedMappingJackson2HttpMessageConverter messageConverter =
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
ObjectMapper objectMapper = messageConverter.getObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.setHandlerInstantiator(
new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null));
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testObject)
.setMessageConverters(messageConverter).build();