MockMVC Integrate test controller with session scoped bean

3.3k views Asked by At

I am trying to integrate test a Spring Controller method that uses a spring session scoped bean which is injected into the controller. In order for my test to pass I must be able to access my session bean to set some values on it before I make my mock call to this controller method. Issue is a new session bean is created when I make the call instead of using the one I pulled of the mock application context. How can I make my controller use the same UserSession bean?

Here is my test case

    @RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration("src/main/webapp")
@ContextConfiguration({"file:src/main/webapp/WEB-INF/applicationContext.xml",
        "file:src/main/webapp/WEB-INF/rest-servlet.xml",
        "file:src/main/webapp/WEB-INF/servlet-context.xml"})
public class RoleControllerIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    protected MockMvc mockMvc;
    protected MockHttpSession mockSession;

    @BeforeClass
    public static void setupClass(){
        System.setProperty("runtime.environment","TEST");
        System.setProperty("com.example.UseSharedLocal","true");
        System.setProperty("com.example.OverridePath","src\\test\\resources\\properties");
        System.setProperty("JBHSECUREDIR","C:\\ProgramData\\JBHSecure");
    }

    @Before
    public void setup(){
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
        mockSession = new MockHttpSession(wac.getServletContext(), UUID.randomUUID().toString());
        mockSession.setAttribute("jbhSecurityUserId", "TESTUSER");
    }

    @Test
    public void testSaveUserRole() throws Exception {

        UserSession userSession = wac.getBean(UserSession.class);
        userSession.setUserType(UserType.EMPLOYEE);
        userSession.setAuthorizationLevel(3);

        Role saveRole = RoleBuilder.buildDefaultRole();
        Gson gson = new Gson();
        String json = gson.toJson(saveRole);

        MvcResult result = this.mockMvc.perform(
                post("/role/save")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(json)
                        .session(mockSession))
                .andExpect(status().isOk())
                .andReturn();

        MockHttpServletResponse response = result.getResponse();

    }

Here is my controller method I am needing tested

    @Resource(name="userSession")
    private UserSession userSession;

    @RequestMapping(method = RequestMethod.POST, value = "/save")
    public @ResponseBody ServiceResponse<Role> saveRole(@RequestBody Role role,HttpSession session){

        if(userSession.isEmployee() && userSession.getAuthorizationLevel() >= 3){
            try {
                RoleDTO savedRole = roleService.saveRole(role,ComFunc.getUserId(session));
                CompanyDTO company = userSession.getCurrentCompany();

It is not passing this line because the UserSession Object is not the same if(userSession.isEmployee() && userSession.getAuthorizationLevel() >= 3){

This is the declaration of my user session bean.

   @Component("userSession")
   @Scope(value="session",proxyMode= ScopedProxyMode.INTERFACES)
   public class UserSessionImpl implements UserSession, Serializable  {

    private static final long serialVersionUID = 1L;

Both controlle and bean are created using component scan in my applicationContext.xml

<context:annotation-config />
    <!-- Activates various annotations to be detected in bean classes -->
    <context:component-scan
        base-package="
            com.example.app.externalusersecurity.bean,
            com.example.app.externalusersecurity.service,
            com.example.app.externalusersecurity.wsc"/>
    <mvc:annotation-driven />
3

There are 3 answers

0
Stefan Sensz On

A bit of a situational case, if anyone will Test with @WebMvcTest then you can also manually trigger the startup of the session scope, like Spring Boot would do by doing the following as an Example in Junit 5:

@ActiveProfiles(profiles = {"TEST"})
@WebMvcTest
@ContextConfiguration(classes = {ApplicationConfiguration.class})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MvcTest{

@Autowired
protected MockMvc mockMvc;

@BeforeAll
public void activateSessionScope() {
    ConfigurableListableBeanFactory clbf = ((AbstractApplicationContext) mockMvc.getDispatcherServlet().getWebApplicationContext()).getBeanFactory();
    WebApplicationContextUtils.registerWebApplicationScopes(clbf, mockMvc.getDispatcherServlet().getServletContext());
}

This results in having your session scope really bound to a session and by that you can manipulate the Session beans values with MockHttpSession.

0
Viswanath On

Add the following bean configuration, which adds a session context for each thread

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="session">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

An equivalent in Java's configuration class would the following bean declaration

@Bean
  public CustomScopeConfigurer scopeConfigurer() {
    CustomScopeConfigurer configurer = new CustomScopeConfigurer();
    Map<String, Object> workflowScope = new HashMap<String, Object>();
    workflowScope.put("session", new SimpleThreadScope());
    configurer.setScopes(workflowScope);

    return configurer;
  }

For more details, see http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/beans.html#beans-factory-scopes-custom-using

0
Markus Pscheidt On

Using different Bean definition profiles for test and production worked for me - here's how a XML based setup could look like:

<beans profile="production">
    <bean id="userSession" class="UserSessionImpl" scope="session" >
        <aop:scoped-proxy/>
    </bean>
</beans>

<beans profile="test">
    <bean id="userSession" class="UserSessionImpl" >
    </bean>
</beans>

To use the test profile for your test, add @ActiveProfiles to your test class:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration("src/main/webapp")
@ContextConfiguration({"file:src/main/webapp/WEB-INF/applicationContext.xml",
    "file:src/main/webapp/WEB-INF/rest-servlet.xml",
    "file:src/main/webapp/WEB-INF/servlet-context.xml"})
@ActiveProfiles(profiles = {"test"})
public class RoleControllerIntegrationTest {
[...]