Spring mvc 3.1 integration tests with session support - java

I'm using the new spring-test in the 3.1 version to run integration tests. It works really well but I can't make the session to work. My code:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration("src/main/webapp")
#ContextConfiguration({"classpath:applicationContext-dataSource.xml",
"classpath:applicationContext.xml",
"classpath:applicationContext-security-roles.xml",
"classpath:applicationContext-security-web.xml",
"classpath:applicationContext-web.xml"})
public class SpringTestBase {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private SessionFactory sessionFactory;
protected MockMvc mock;
protected MockHttpSession mockSession;
#Before
public void setUp() throws Exception {
initDataSources("dataSource.properties");
mock = MockMvcBuilders.webAppContextSetup(wac).addFilters(springSecurityFilterChain).build();
mockSession = new MockHttpSession(wac.getServletContext(), UUID.randomUUID().toString());
}
#Test
public void testLogin() throws Exception {
// this controller sets a variable in the session
mock.perform(get("/")
.session(mockSession))
.andExpect(model().attributeExists("csrf"));
// I set another variable here just to be sure
mockSession.setAttribute(CSRFHandlerInterceptor.CSRF, csrf);
// this call returns 403 instead of 200 because the session is empty...
mock.perform(post("/setup/language")
.session(mockSession)
.param(CSRFHandlerInterceptor.CSRF, csrf)
.param("language", "de"))
.andExpect(status().isOk());
}
}
My session is empty in every request, I don't know why.
EDIT: The last assert is failing: andExpect(status().isOk());. It returns 403 instead of 200.

UPDATED ANSWER:
It seems a new method "sessionAttrs" has been added to the builder (see mvc controller test with session attribute)
Map<String, Object> sessionAttrs = new HashMap<>();
sessionAttrs.put("sessionAttrName", "sessionAttrValue");
mockMvc.perform(MockMvcRequestBuilders.get("/uri").sessionAttrs(sessionAttrs))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk());
OLD ANSWER:
here is a simpler solution to achieve the same result without using supporting classes, this is a snippet of my code (I don't know if these methods had been already available when Biju Kunjummen answered):
HttpSession session = mockMvc.perform(post("/login-process").param("j_username", "user1").param("j_password", "user1"))
.andExpect(status().is(HttpStatus.FOUND.value()))
.andExpect(redirectedUrl("/"))
.andReturn()
.getRequest()
.getSession();
Assert.assertNotNull(session);
mockMvc.perform(get("/").session((MockHttpSession)session).locale(Locale.ENGLISH))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("logged_in"));

I have done this in a somewhat roundabout manner - works though. What I did was to let Spring-Security create a session with the relevant Security attributes populated in the session and then grab that session this way:
this.mockMvc.perform(post("/j_spring_security_check")
.param("j_username", "fred")
.param("j_password", "fredspassword"))
.andExpect(status().isMovedTemporarily())
.andDo(new ResultHandler() {
#Override
public void handle(MvcResult result) throws Exception {
sessionHolder.setSession(new SessionWrapper(result.getRequest().getSession()));
}
});
SessionHolder is my custom class, just to hold the session:
private static final class SessionHolder{
private SessionWrapper session;
public SessionWrapper getSession() {
return session;
}
public void setSession(SessionWrapper session) {
this.session = session;
}
}
and SessionWrapper is another class extending from MockHttpSession, just because the session method requires MockHttpSession:
private static class SessionWrapper extends MockHttpSession{
private final HttpSession httpSession;
public SessionWrapper(HttpSession httpSession){
this.httpSession = httpSession;
}
#Override
public Object getAttribute(String name) {
return this.httpSession.getAttribute(name);
}
}
With these set, now you can simply take the session from the sessionHolder and execute subsequent methods, for eg. in my case:
mockMvc.perform(get("/membersjson/1").contentType(MediaType.APPLICATION_JSON).session(sessionHolder.getSession()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("OneUpdated")));

Related

Spring Boot: How to change the Content Security Policy at runtime?

I'm trying to hot-reload a change in the content security policy (CSP) of my Spring Boot application, i.e. the user should be able to change it via an admin UI without restarting the server.
The regular approach in Spring Boot is:
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) {
// ... lots more config here...
http.headers()
.addHeaderWriter(
StaticHeadersWriter(
"Content-Security-Policy",
"<some policy string>"
)
)
}
}
... but this doesn't allow for reconfiguration once it has been assigned.
Can I make this (re-)configurable at runtime? Reloading the application context is not an option, I need to be able to adapt only this particular setting.
Easy-Peasy, we only need to expose a (n appropriate) HeaderWriter as a bean! ContentSecurityPolicyHeaderWriter looks appropriate & sufficient for us, but we are also free to implement a custom:
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Bean
public ContentSecurityPolicyHeaderWriter myWriter(
#Value("${#my.policy.directive:DEFAULT_SRC_SELF_POLICY}") String initalDirectives
) {
return new ContentSecurityPolicyHeaderWriter(initalDirectives);
}
Then with:
#Autowired
private ContentSecurityPolicyHeaderWriter myHeadersWriter;
#Override
public void configure(HttpSecurity http) throws Exception {
// ... lots more config here...
http.headers()
.addHeaderWriter(myHeadersWriter);
}
..., we can change the header value with these demo controllers:
#GetMapping("/")
public String home() {
myHeadersWriter.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
return "header reset!";
}
#GetMapping("/foo")
public String foo() {
myHeadersWriter.setPolicyDirectives("FOO");
return "Hello from foo!";
}
#GetMapping("/bar")
public String bar() {
myHeadersWriter.setPolicyDirectives("BAR");
return "Hello from bar!";
}
We can test:
#SpringBootTest
#AutoConfigureMockMvc
class DemoApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testHome() throws Exception {
this.mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("header reset!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, DEFAULT_SRC_SELF_POLICY));
}
#Test
public void testFoo() throws Exception {
this.mockMvc.perform(get("/foo"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from foo!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "FOO"));
}
#Test
public void testBar() throws Exception {
this.mockMvc.perform(get("/bar"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from bar!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "BAR"));
}
}
... also in browser:
All in one github.(sorry all in main class!:)
Refs: only this
The problem with the (my) accepted answer is:
(just for the show case, but:) We modify "singleton scope property" on (every) request!!!
When we add a "stress" test wrapper like this.
( ... wait until all threads finish their work in java ?? -> ExecutorCompletionService, since Java:1.5;)
It badly fails (header has not the "expected" value):
#Test
void testParallel() throws Exception {
// 200 cycles, with 0 (== #cpu) threads ...
final StressTester<Void> stressTestHome = new StressTester<>(Void.class, 200, 0, // ... and these (three) jobs (firing requests at our app):
() -> {
home(); // here the original tests
return null;
},
() -> {
foo(); // ... with assertions ...
return null;
},
() -> {
bar(); // ... moved to private (non Test) methods
return null;
}
);
stressTestHome.test(); // run it, collect it and:
stressTestHome.printErrors(System.out);
assertTrue(stressTestHome.getExceptionList().isEmpty());
}
As in mock as in (full) server mode... ;(;(;(
We will encounter the same problem, when we want to change that header from a "lower scope" (than singleton..so any other scope:) ;(;(;(
If we want singleton scope policy for that header, and only "trigger the reload" (for all subsequent requests), we can stop reading. (answer 1 is ok, as i actually "initially understood" the question & answered:)
But if we want that "per request header" with spring-security, we have to pass this test! :)
One possible solution: Method Injection!
So back to our custom HeaderWriter implementation:
package com.example.demo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
// abstract!
public abstract class MyContentSecurityPolicyHeaderWriter implements HeaderWriter {
// ... no state!!!
public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
public static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
#Override // how cool, that there is a HttpServletRequest/-Response "at hand" !?!
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (!response.containsHeader(CONTENT_SECURITY_POLICY_HEADER)) {
// responsible for the header key, but for the value we ask: delegate
response.setHeader(CONTENT_SECURITY_POLICY_HEADER, policyDelegate().getPolicyDirectives());
}
}
// TLDR xDxD
protected abstract MyContentSecurityDelegate policyDelegate();
}
Thanks, again!;)
With this tiny (but managed) "context holder":
package com.example.demo;
import lombok.*;
#NoArgsConstructor
#AllArgsConstructor(staticName = "of")
public class MyContentSecurityDelegate {
#Getter
#Setter
private String policyDirectives;
}
We do this (with spring-java-config, How to create bean using #Bean in spring boot for abstract class):
#Configuration
class FreakyConfig {
#Value("${my.policy.directive:DEFAULT_SRC_SELF_POLICY}")
private String policy;
#Bean
#RequestScope // !! (that is suited for our controllers)
public MyContentSecurityDelegate delegate() {
return MyContentSecurityDelegate.of(policy);
}
#Bean
public MyContentSecurityPolicyHeaderWriter myWriter() {
return new MyContentSecurityPolicyHeaderWriter() { // anonymous inner class
#Override
protected MyContentSecurityDelegate policyDelegate() {
return delegate(); // with request scoped delegate.
}
};
}
}
..then our controllers do that (autowire & "talk" to the delegate):
#Autowired // !
private MyContentSecurityDelegate myRequestScopedDelegate;
#GetMapping("/foo")
public String foo() {
// !!
myRequestScopedDelegate.setPolicyDirectives("FOO");
return "Hello from foo!";
}
Then all tests pass! :) pushed to (same)github.
But to achieve the goal: "Write headers request (even thread) specific", we can use any other technique (matching our stack & needs, beyond spring-security):
with or without spring-boot
servlet
with spring-mvc/without
javax.servlet.*:
Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container..
from Registering Servlets, Filters, and Listeners as Spring Beans
or reactive ...
Mo' Links:
How can I add a filter class in Spring Boot?
https://www.baeldung.com/spring-response-header
https://www.baeldung.com/spring-boot-add-filter
Happy Coding!

How can test a Spring Boot controller method annoted with #PreAuthorized(hasAnyAuthority(...))

My controller class is a follows:
#PostMapping(path="/users/{id}")
#PreAuthorize("hasAnyAuthority('CAN_READ')")
public #ResponseBody ResponseEntity<User> getUser(#PathVariable int id) {
...
}
I have the following Resource Server config
#Configuration
public class ResourceServerCofig implements ResourceServerConfigurer {
private static final String RESOURCE_ID = "test";
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
}
Finally my test looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientController.class)
public class ClientControllerTest {
#Autowired
private MockMvc mockMvc;
#WithMockUser(authorities={"CAN_READ"})
#Test
public void should_get_user_by_id() throws Exception {
...
mockMvc.perform(MockMvcRequestBuilders.get("/user/1")).
andExpect(MockMvcResultMatchers.status().isOk()).
andExpect(MockMvcResultMatchers.header().string(HttpHeaders.CONTENT_TYPE, "application/json")).
andExpect(MockMvcResultMatchers.jsonPath("$.name").value("johnson"));
}
}
Issue is I always get a 401 HTTP status with message unauthorized","error_description":"Full authentication is required to access this resource.
How can should I write tests for #PreAuthorized annotated controller methods methods?
I've spent part of the day figuring out how to solve this. I ended up with a solution that I think is not so bad, and could help many.
Based on what you tried to do in your test, you can do a mockMvc to test your controller. Notice that the AuthorizationServer is not called. You stay only in your Resource server for the tests.
Create a bean InMemoryTokenStore that will be used in the OAuth2AuthenticationProcessingFilter to authenticate your user. What is great is that you can add tokens to your InMemoryTokenStore before executing your tests. The OAuth2AuthenticationProcessingFilter will authenticate the user based on the token he is using and will not call any remote server.
#Configuration
public class AuthenticationManagerProvider {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
The annotation #WithMockUser does not work with OAuth2. Indeed, the OAuth2AuthenticationProcessingFilter always checks your token, wi no regard to the SecurityContext. I suggest to use the same approach as #WithMockUser, but using an annotation you create in your code base. (To have some easy to maintain and clean tests):
#WithMockOAuth2Scope contains almost all the parameters you need to customize your authentication. You can delete those you will never use, but I put a lot to make sure you see the possibilities. (Put those 2 classes in your test folder)
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public #interface WithMockOAuth2Scope {
String token() default "";
String clientId() default "client-id";
boolean approved() default true;
String redirectUrl() default "";
String[] responseTypes() default {};
String[] scopes() default {};
String[] resourceIds() default {};
String[] authorities() default {};
String username() default "username";
String password() default "";
String email() default "";
}
Then, we need a class to interpret this annotation and fill our `InMemoryTokenStore with the data you need for your test.
#Component
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
#Autowired
private TokenStore tokenStore;
#Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2AccessToken oAuth2AccessToken = createAccessToken(mockOAuth2Scope.token());
OAuth2Authentication oAuth2Authentication = createAuthentication(mockOAuth2Scope);
tokenStore.storeAccessToken(oAuth2AccessToken, oAuth2Authentication);
return SecurityContextHolder.createEmptyContext();
}
private OAuth2AccessToken createAccessToken(String token) {
return new DefaultOAuth2AccessToken(token);
}
private OAuth2Authentication createAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2Request oauth2Request = getOauth2Request(mockOAuth2Scope);
return new OAuth2Authentication(oauth2Request,
getAuthentication(mockOAuth2Scope));
}
private OAuth2Request getOauth2Request(WithMockOAuth2Scope mockOAuth2Scope) {
String clientId = mockOAuth2Scope.clientId();
boolean approved = mockOAuth2Scope.approved();
String redirectUrl = mockOAuth2Scope.redirectUrl();
Set<String> responseTypes = new HashSet<>(asList(mockOAuth2Scope.responseTypes()));
Set<String> scopes = new HashSet<>(asList(mockOAuth2Scope.scopes()));
Set<String> resourceIds = new HashSet<>(asList(mockOAuth2Scope.resourceIds()));
Map<String, String> requestParameters = Collections.emptyMap();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
return new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
}
private Authentication getAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
String username = mockOAuth2Scope.username();
User userPrincipal = new User(username,
mockOAuth2Scope.password(),
true, true, true, true, grantedAuthorities);
HashMap<String, String> details = new HashMap<>();
details.put("user_name", username);
details.put("email", mockOAuth2Scope.email());
TestingAuthenticationToken token = new TestingAuthenticationToken(userPrincipal, null, grantedAuthorities);
token.setAuthenticated(true);
token.setDetails(details);
return token;
}
}
Once everything is setup, create a simple Test class under src/test/java/your/package/. This class will do the mockMvc operations, and use the # WithMockOAuth2Scope to create the token you need for your test.
#WebMvcTest(SimpleController.class)
#Import(AuthenticationManagerProvider.class)
class SimpleControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithMockOAuth2Scope(token = "123456789",
authorities = "CAN_READ")
public void test() throws Exception {
mockMvc.perform(get("/whoami")
.header("Authorization", "Bearer 123456789"))
.andExpect(status().isOk())
.andExpect(content().string("username"));
}
}
I came up with this solutions thanks to:
How to test spring-security-oauth2 resource server security?
Faking OAuth2 Single Sign-on in Spring, Two Ways
Annotation solution
Fluent API solution
A lot of debug, where I lost myself inside Spring, but I finally found what I was looking for.
For the curious:
When testing, Spring loads a InMemoryTokenStore, and give you the possibility to take one you provide (as a #Bean). When running in production, for my case, Spring uses a RemoteTokenStore, which calls the remote Authorization server to check the token (http://authorization_server/oauth/check_token).
When you decide to use OAuth2, Spring fires the OAuth2AuthenticationProcessingFilter. This was my entry point during all me debugging sessions.
I've learned a lot, thank you for this.
You can find the source code here.
Hope it will help !

Correct configuration to mock Hibernate's sessionFactory.getCurrentSession()

I have the following:
#Repository
#Transactional
#HibernateProfile
public class PersonaHibernateRepository implements PersonaRepository {
private static final Logger logger = LoggerFactory.getLogger(PersonaHibernateRepository.class.getSimpleName());
private final SessionFactory sessionFactory;
public PersonaHibernateRepository(SessionFactory sessionFactory){
logger.info("{} constructor", PersonaHibernateRepository.class.getSimpleName());
this.sessionFactory = sessionFactory;
}
#Override
public Persona saveOne(Persona persona) {
String generatedIdentifier = (String) sessionFactory.getCurrentSession().save(persona);
logger.info("generatedIdentifier: {}", generatedIdentifier);
return persona;
}
...
Each crud method has sessionFactory.getCurrentSession().
With Mockito the following sentence:
when(sessionFactory.getCurrentSession().save(persona)).thenReturn(persona.getId());
always throws java.lang.NullPointerException. I've confirmed sessionFactory.getCurrentSession() is the point of the problem.
I already have read the following:
Mocking Hibernate Session
Unit test of DAO layer with mockito
Thus the java.lang.NullPointerException was removed.
But I always get now:
org.mockito.exceptions.verification.TooManyActualInvocations:
sessionFactory.getCurrentSession();
Wanted 1 time:
-> at com.manuel.jordan.repository.hibernate.PersonaHibernateRepositoryTest_.saveOneTest(PersonaHibernateRepositoryTest_.java:76)
But was 2 times. Undesired invocation:
-> at com.manuel.jordan.repository.hibernate.PersonaHibernateRepository.saveOne(PersonaHibernateRepository.java:43)
These two times happens due the mock invocation and target invocation.
Currently my configuration is:
private PersonaHibernateRepository personaHibernateRepository;
private SessionFactory sessionFactory;
private Session session;
...
#Before
public void setup(){
sessionFactory = mock(SessionFactory.class);
session = mock(Session.class);
personaHibernateRepository = new PersonaHibernateRepository(sessionFactory);
//Removes NullPointerException - 'A'
when(sessionFactory.getCurrentSession()).thenReturn(session);
}
#Test
public void saveOneTest(){
//java.lang.NullPointerException removed thanks to 'A'
when(sessionFactory.getCurrentSession().save(persona)).thenReturn(persona.getId());
Persona persona_ = personaHibernateRepository.saveOne(persona);
assertThat(persona_, is(persona));
//B
verify(sessionFactory).getCurrentSession().save(persona);
}
Just playing, if I change:
From: verify(sessionFactory).getCurrentSession().save(persona);
To: verify(sessionFactory, times(2)).getCurrentSession().save(persona); (observe times(2))
Again appears the java.lang.NullPointerException thrown now by verify(sessionFactory, times(2)).getCurrentSession().save(persona); (B)
Same exception if in #Before the when(sessionFactory.getCurrentSession()).thenReturn(session) is changed to doReturn(session).when(sessionFactory).getCurrentSession()
What is the correct configuration?
In the setup() method you correctly tell Mockito to return the mocked session instance when sessionFactory.getCurrentSession() is invoked so from them on your assertions should focus on the session instance not the sessionFactory. For example:
#Test
public void saveOneTest(){
// you have already told Mockito to return this session instance when sessionFactory.getCurrentSession() is
// invoked so now your when (and optionally verify) should focus on session rather than on sessionFactory
when(session.save(persona)).thenReturn(persona.getId());
Persona persona_ = personaHibernateRepository.saveOne(persona);
assertThat(persona_, is(persona));
verify(session).save(persona);
}

org.hibernate.LazyInitializationException: could not initialize proxy -no Session

I am getting this bug when I run a test case(Junit) for spring application.
I searched for this problem and I got the information that whenever a lazy initialization occurs and my application tries to get second level data while session is closed(object become detached) then this error occurs, we can't make initialization as EAGER as its performance issue.
My testing class contains :
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTestClass extends AbstractControllerTest {
#Rule
public TestName testMethodName = new TestName();
#Before
public void setUp() throws Exception
{
super.setUp();
}
#After
public void tearDown() throws Exception
{
super.tearDown();
}
#Test
public void myTestMethod ()
{
assertTrue("Response Validating",validate(baseEntity,perform()));
}
}
Is there a way that can I put method assertTrue("Response Validating",validate(baseEntity,perform())); in a transaction can bind with current session or with new session so that my detached object become persistent object and then My application can get second level data also. I searched for this problem and I found a solution on link : http://www.jroller.com/RickHigh/entry/hibernate_spring_simulating_an_opensessioninviewfilter
but this link does not fulfil my requirement as it requires target object on which transaction is to be created.
#Test
#Transactional
public void myTestMethod ()
{
assertTrue("Response Validating",validate(baseEntity,perform()));
}
Annotate myTestMethod with #Transactional (assuming you're using annotation-based configuration).
I got the solution for this problem. I was implementing OpenSessionInViewFilter in my testing code to overcome this problem but was doing silly mistake.
Please take a look on following code :
#Autowired
BeanFactory bf;
#Before
public void setUp() throws Exception
{
sessionFactory = (SessionFactory) bf.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
#After
public void tearDown() throws Exception
{
super.tearDown();
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
Session session = sessionHolder.getSession();
SessionFactoryUtils.closeSession(session);
}
Earlier I was not using
session.setFlushMode(FlushMode.NEVER) this was the mistake.
BTW thanks

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