Related
I am trying to do integration tests which include the execution of an ApplicationRunner.
I use #SpringBootTest, and when the context is initialized, the ApplicationRunner is automatically launched, which is okay.
What I want to achieve is to inject a Mock in the ApplicationRunner AND configure that mock BEFORE the ApplicationRunner is executed.
It doesn't work if I configure the mock IN the test, because by the time the test is executed, the Spring context has already been initialized and the ApplicationRunner has already been executed.
Is there a way to configure the mock after the Spring context has been initialized, and before the ApplicationRunner is executed?
If you have a bean dependency for your ApplicationRunner class, You can mock it as below.
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
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.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ApplicationRunnerTest {
#MockBean
private final Dependency dependency;
#Autowired
private final ApplicationRunner applicationRunner;
#Before
public void setUp() throws Exception {
}
#Test
public void testMethod() {
}
}
Hope this answers your question
Here webApplicationContext is initialised whenever we try to execute a test case as #Before get initiated which calls setUp() of AbstractTest class which has initialisation logic of webApplicationContext
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public abstract class AbstractTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
protected String mapToJson(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
}
public class UserControllerTest extends AbstractTest {
#Override
#Before
public void setUp() {
super.setUp();
}
#Test
public void testGet() throws Exception {
String uri = “/url”;
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(uri)
.accept(MediaType.APPLICATION_JSON_VALUE)).andReturn();
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
}
}
Main Spring boot class
#SpringBootApplication
public class Main extends SpringBootServletInitializer{
private Logger LOGGER = (Logger) LoggerFactory.getLogger(FactsMain.class);
#Value("${facts.trustCertPath}")
private String trustCertPath;
public static void main(String args[]) {
SpringApplication.run(Main.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(FactsMain.class);
}
#PostConstruct
public void setSSLPath() {
LOGGER.info("trustCertPath - " + trustCertPath);
System.setProperty("javax.net.ssl.trustStore", trustCertPath);
}
}
I have Spring Integration test where I'm trying to Mock some of my Beans. For some reason although I Mocked them they are NULL. Here is code snippet:
The Bean which I want to Mock
#Component
public class MockWS {
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
The class where the Bean is used
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private MockWS mock;
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Spring boot integration test where the Bean has been mocked
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean(name="mockWS")
MockWS mockService;
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}
Output from the build execution: TEST BEAN: null
In my case the following combination of annotations worked:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { ControllerThatIAmTesting.class })
#AutoConfigureMockMvc(addFilters = false) // if using MockMvc object
But I had to declare explicitly both Autowired objects that I use in the ControllerThatIAmTesting in the test class with #MockBean annotation - otherwise Spring would complain that it cannot find suitable implementation - incidentally both my interfaces and their implementations are in the same corresponding packages
Also, using #WebMvcTest instead of #SpringBootTest (other suggest it as more specific scenario) resulted in Spring failing to find and initialize some other #Autowired dependencies from my #Configuration classes.
Related posts post1 post2 post3
You should mock an interface, not a class. Also, SmDpES2PortImpl must be a Spring bean. Try the following:
Interface:
public interface IMockWS {
public String callSoapClient() throws JAXBException;
}
Component class:
#Component
public class MockWS implements IMockWS {
#Override
public String callSoapClient() throws JAXBException{
return "CallSoapCl";
}
}
Service class:
#Service //Also #Component is a good alternative
public class SmDpES2PortImpl implements ES2SmDp {
#Autowired
private IMockWS mock; //Notice that you are wiring an interface
#Override
public void es2DownloadProfile(ES2DownloadProfileRequest parameters) {
try {
LOG.info("\n\n\n TEST BEAN: " + mock.callSoapClient() + "\n\n");
}
}
}
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ES2SmDpApplicationTests {
#MockBean
IMockWS mockService; //Again, you are mocking the interface, not the implementing class
#Test
public void test1Es2DownloadProfile_Sucess() throws MalformedURLException, JAXBException, SOAPException {
when(mockService.callSoapClient()).thenReturn("CallMockCLient");
}
}
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"})
I want to test a Resourse with JerseyTest. I have created the following test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:testApplicationContext.xml")
public class ResourceTest extends JerseyTest
{
#Configuration
public static class Config
{
#Bean
public AObject aObject()
{
return mock(AObject.class);
}
}
#Autowired
public AObject _aObject;
#Test
public void testResource()
{
// configouring mock _aObject
Response response = target("path");
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}
#Override
protected Application configure()
{
return new ResourceConfig(Resource.class).property("contextConfigLocation", "classpath:testApplicationContext.xml");
}
}
My Resource also has an AObject reference with #Autowired annotation.
My problem is that my JerseyTest and the Resource (that is configured by the test) have different instances for the Mock object. In the console I see that the testApplicationContext.xml is loaded twice, once for the test and one for the Resource.
How can I force jersey to use the same mock?
After debugging the jersey-spring3 (version 2.9.1) library it seems that the problem lies in the SpringComponentProvider.createSpringContext
private ApplicationContext createSpringContext() {
ApplicationHandler applicationHandler = locator.getService(ApplicationHandler.class);
ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration().getProperty(PARAM_SPRING_CONTEXT);
if (springContext == null) {
String contextConfigLocation = (String) applicationHandler.getConfiguration().getProperty(PARAM_CONTEXT_CONFIG_LOCATION);
springContext = createXmlSpringConfiguration(contextConfigLocation);
}
return springContext;
}
It checks if a property named "contextConfig" exists in the application properties and if not it initializes the spring application context.
Even if you initialized a spring application context in your tests, jersey will create another context and use that one instead. So we have to somehow pass the ApplicationContext from our tests in the Jersey Application class. The solution is the following:
#ContextConfiguration(locations = "classpath:jersey-spring-applicationContext.xml")
public abstract class JerseySpringTest
{
private JerseyTest _jerseyTest;
public final WebTarget target(final String path)
{
return _jerseyTest.target(path);
}
#Before
public void setup() throws Exception
{
_jerseyTest.setUp();
}
#After
public void tearDown() throws Exception
{
_jerseyTest.tearDown();
}
#Autowired
public void setApplicationContext(final ApplicationContext context)
{
_jerseyTest = new JerseyTest()
{
#Override
protected Application configure()
{
ResourceConfig application = JerseySpringTest.this.configure();
application.property("contextConfig", context);
return application;
}
};
}
protected abstract ResourceConfig configure();
}
The above class will take the application context from our tests and pass it to the configured ResourceConfig, so that the SpringComponentProvider will return the same application context to jersey. We also use the jersey-spring-applicationContext.xml in order to include jersey specific spring configuration.
We cannot inherit from JerseyTest because it initializes the Application in the constructor before the test application context is initialized.
You can now use this base class to create your tests for example
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:testContext.xml")
public class SomeTest extends JerseySpringTest
{
#Autowired
private AObject _aObject;
#Test
public void test()
{
// configure mock _aObject when(_aObject.method()).thenReturn() etc...
Response response = target("api/method").request(MediaType.APPLICATION_JSON).get();
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}
#Override
protected ResourceConfig configure()
{
return new ResourceConfig(MyResource.class);
}
}
In testContext.xml add the following definition in order to inject a mock AObject.
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.yourcompany.AObject" />
</bean>
I couldn't get the answer https://stackoverflow.com/a/24512682/156477 from #Grigoris working, although his explanation for why it is happening is correct.
In the end I went for the approach below which exposes a special setter to insert the mock object. Not as 'clean' as the approach above, but worth the tradeoff of exposing the apiProvider I wanted to mock so I could write some tests..
public MyAPITest extends JerseyTest {
// Declare instance of the API I want to test - this will be instantiated in configure()
MyAPI myAPI;
#Override
protected ResourceConfig configure()
{
MockitoAnnotations.initMocks(this);
myAPI = new MyAPI();
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(MyAPI).property("contextConfig", new ClassPathXmlApplicationContext("classpath:spring.testHarnessContext.xml"));
return resourceConfig;
}
#Mock
private MyAPIProvider mockAPIProvider;
#Before
public void before() {
myAPI.setMockProvider(mockAPIProvider);
}
#Test
public void test() {
// I can now define the mock behaviours and call the API and validate the outcomes
when(mockAPIProvider....)
target().path("....)
}
}
If anyone is interested in the solution https://stackoverflow.com/a/40591082/4894900 from Kevin for Jersey v1:
public MyAPITest extends JerseyTest {
#InjectMocks
MyAPI myAPI;
#Mock
MyApiService myApiService;
#Override
protected AppDescriptorconfigure()
{
MockitoAnnotations.initMocks(this);
ResourceConfig rc = new DefaultResourceConfig();
rc.getSingletons().add(myAPI);
return new LowLevelAppDescriptor.Builder(rc).contextPath("context").build();
}
#Test
public void test() {
// I can now define the mock behaviours
when(myApiService...)
WebResource webResource = resource().path("mypath");
ClientResponse result = webResource.get(ClientResponse.class);
}
}
Further improvising the accepted solution by removing xml dependency. More details available here.
JerseySpringTest abstracting JerseyTest:
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
/** Run JerseyTest with custom spring context with mocks Mimics Spring #WebMvcTest with #MockBean */
public abstract class JerseySpringTest extends JerseyTest {
#Override
protected ResourceConfig configure() {
MockitoAnnotations.openMocks(this);
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
set(TestProperties.CONTAINER_PORT, "0");
final ResourceConfig resourceConfig =
new ResourceConfig()
.property("contextConfig", createSpringContext(getBeanMap()))
.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, "WARNING")
.register(getResourceClass());
return serverConfig(resourceConfig);
}
/**
* Gives the test class opportunity to further customize the configuration. Like registering a
* MultiPartFeature if required.
*
* #param config
* #return
*/
protected ResourceConfig serverConfig(final ResourceConfig config) {
return config;
}
/**
* Supplies all the bean objects required to be loaded in the application context for the Resource class
* under test
*
* #return
*/
protected List<Object> getBeans() {
return Collections.emptyList();
}
/**
* Supplies all the bean objects with name qualifier required to be loaded in the application context for the Resource class
* under test
*
* #return
*/
protected Map<String, Object> getQualifiedBeans() {
return Collections.emptyMap();
}
private Map<String, Object> getBeanMap() {
final Map<String, Object> result = new HashMap<>();
CollectionUtils.emptyIfNull(getBeans())
.forEach(obj -> result.put(StringUtils.uncapitalize(obj.getClass().getSimpleName()), obj));
result.putAll(MapUtils.emptyIfNull(getQualifiedBeans()));
return result;
}
/**
* Resource class under test
*
* #return
*/
protected abstract Class<?> getResourceClass();
/**
* Creates & returns a Spring GenericApplicationContext from the given beans with qualified names
*
* #param beans
* #return
*/
public static ApplicationContext createSpringContext(Map<String, Object> beans) {
final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
MapUtils.emptyIfNull(beans).forEach((k, obj) -> beanFactory.registerSingleton(k, obj));
final GenericApplicationContext context = new GenericApplicationContext(beanFactory);
context.refresh();
return context;
}
}
Sample Resource With Test:
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import lombok.RequiredArgsConstructor;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Path("/rest")
#Component
#RequiredArgsConstructor
class RestResource {
private final ServiceXYZ serviceXYZ;
private final ServiceABC serviceABC;
#Qualifier("greeter")
private final String greeterName;
#GET
#Path("/serviceXYZ/greet/{name}")
public Response greetByServiceXYZ(#PathParam("name") final String name) {
return Response.ok(serviceXYZ.greet(name) + ", Regards: " + greeterName).build();
}
#GET
#Path("/serviceABC/greet/{name}")
public Response greetByServiceABC(#PathParam("name") final String name) {
return Response.ok(serviceABC.greet(name)+ ", Regards: " + greeterName).build();
}
}
#Service
class ServiceXYZ {
public final String greet(final String name) {
return "Welcome " + name + " to Hello World!";
}
}
#Service
class ServiceABC {
public final String greet(final String name) {
return "Welcome " + name + " to Hello Universe!";
}
}
class ResourceTest extends JerseySpringTest {
#InjectMocks private RestResource subject;
#Mock private ServiceXYZ serviceXYZ;
#Mock private ServiceABC serviceABC;
// only required to override for custom server config, say if the Resource accepts file input
#Override
protected ResourceConfig serverConfig(final ResourceConfig config) {
return config.register(MultiPartFeature.class);
}
#Override
protected Map<String, Object> getQualifiedBeans() {
return Map.of("greeter", "Amith Kumar");
}
#Override
protected List<Object> getBeans() {
return List.of(subject, serviceXYZ, serviceABC);
}
#Override
protected Class<?> getResourceClass() {
return RestResource.class;
}
// only required to override for custom client config, say if the Resource accepts file input
#Override
protected void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}
#Test
void testServiceXYZGreets() {
// ARRANGE
when(serviceXYZ.greet("foo")).thenReturn("Hello foo");
// ACT
Response output = target("/rest/serviceXYZ/greet/foo").request().get();
// ASSERT
assertAll(
() -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
() -> assertEquals("Hello foo, Regards: Amith Kumar", output.readEntity(String.class)));
}
#Test
void testServiceABCGreets() {
// ARRANGE
when(serviceXYZ.greet("boo")).thenReturn("Hola boo");
// ACT
Response output = target("/rest/serviceABC/greet/boo").request().get();
// ASSERT
assertAll(
() -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
() -> assertEquals("Hola boo, Regards: Amith Kumar", output.readEntity(String.class)));
}
}
I have a simple annotated controller similar to this one:
#Controller
public class MyController {
#RequestMapping("/{id}.html")
public String doSomething(#PathVariable String id, Model model) {
// do something
return "view";
}
}
and I want to test it with an unit test like this:
public class MyControllerTest {
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
new AnnotationMethodHandlerAdapter()
.handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
}
The problem is that AnnotationMethodHandlerAdapter.handler() method throws an exception:
java.lang.IllegalStateException: Could not find #PathVariable [id] in #RequestMapping
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.java:642)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:514)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:262)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:146)
I'd call what you're after an integration test based on the terminology in the Spring reference manual. How about doing something like:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "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;
private MyController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// I could get the controller from the context here
controller = new MyController();
}
#Test
public void testDoSomething() throws Exception {
request.setRequestURI("/test.html");
final ModelAndView mav = handlerAdapter.handle(request, response,
controller);
assertViewName(mav, "view");
// assert something
}
}
For more information I've written a blog entry about integration testing Spring MVC annotations.
As of Spring 3.2, there is a proper way to test this, in an elegant and easy way. You will be able to do things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("servlet-context.xml")
public class SampleTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().mimeType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
For further information, take a look at http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/
A promising framework for testing Spring MVC
https://github.com/SpringSource/spring-test-mvc
The exception message refers to a "feed" variable, which isn't present in your sample code, it's likely being caused by something you haven't shown us.
Also, your test is testing Spring and your own code. Is this really what you want to do?
It's better to assume that Spring works (which it does), and just test your own class, i.e. call MyController.doSomething() directly. That's one benefit of the annotation approach - you don't need to use mock requests and responses, you just use domain POJOs.
I've found that you can manually insert a PathVariable mapping into the request object. This is distinctly non-ideal but appears to work. In your example, something like:
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
pathvars.put("id", "test");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
new AnnotationMethodHandlerAdapter().handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
I'd definitely be interested in finding a better option.
Provided you are using Spring 3.0.x.
Here I suggest a merger of Emil and scarba05 answers using spring-test not spring-test-mvc. Please skip this answer and refer to spring-test-mvc examples if you are using Spring 3.2.x or later
MyControllerWithParameter.java
#Controller
public class MyControllerWithParameter {
#RequestMapping("/testUrl/{pathVar}/some.html")
public String passOnePathVar(#PathVariable String pathVar, ModelMap model){
model.addAttribute("SomeModelAttribute",pathVar);
return "viewName";
}
}
MyControllerTest.java
import static org.springframework.test.web.ModelAndViewAssert.assertViewName;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations =
{"file:src\\main\\webapp\\WEB-INF\\spring\\services\\servlet-context.xml"
})
public class MyControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
}
// Container beans
private MyControllerWithParameter myController;
private ApplicationContext applicationContext;
public ApplicationContext getApplicationContext() {
return applicationContext;
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public MyControllerWithParameter getMyController() {
return myController;
}
#Autowired
public void setMyController(MyControllerWithParameter myController) {
this.myController = myController;
}
#Test
public void test() throws Exception {
request.setRequestURI("/testUrl/Irrelavant_Value/some.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
// Populate the pathVariable-value pair in a local map
pathvars.put("pathVar", "Path_Var_Value");
// Assign the local map to the request attribute concerned with the handler mapping
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
final ModelAndView modelAndView = this.handlerAdapter.handle(request, response, myController);
ModelAndViewAssert.assertAndReturnModelAttributeOfType(modelAndView, "SomeModelAttribute", String.class);
ModelAndViewAssert.assertModelAttributeValue(modelAndView, "SomeModelAttribute", "Path_Var_Value");
ModelAndViewAssert.assertViewName(modelAndView, "viewName");
}
}
I'm not sure my original answer is going to help with #PathVariable. I've just tried testing an #PathVariable and I get the following exception:
org.springframework.web.bind.annotation.support.HandlerMethodInvocationException: Failed to invoke handler method [public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod(test.SomeType)]; nested exception is java.lang.IllegalStateException: Could not find #PathVariable [parameterName] in #RequestMapping
The reason is that the path variables in the request get parsed by an interceptor. The following approach works for me:
import static org.springframework.test.web.ModelAndViewAssert.*;
#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 testDoSomething() throws Exception {
request.setRequestURI("/test.html");
request.setMethod("GET");
final ModelAndView mav = handle(request, response);
assertViewName(mav, "view");
// assert something else
}
I've add a new blog post on integration testing spring mvc annotations