[JAVA] Resolve null pointers for various sessions during MVC testing of Spring boot.

In the spring boot application you are developing When I was testing mvc with MockMvc Since an exception is thrown with a null pointer when getting the session variable, the solution in that case is summarized.

I think there may be misunderstandings and other methods. We will fix it as soon as we find a new method or a better method. We would appreciate it if you could point out any mistakes in the description or recognition. Thank you for your valuable time at that time.

Do not write

· Test setup (set of gradle and mvn) -In the first place, it is a different matter whether the description using session and how to make it are correct (I think there is another solution), so I will not touch that part. Will study. -The description method around authentication is not written.

.with(user(mockuser))It is authenticated and executed at.



### environment
spring boot 1.3.x
java 1.8


## background

 When doing MVC testing, the controller method
 After executing, it becomes a null pointer when referencing a session instance.
 I was in trouble because I couldn't test.
 When you think about it, you don't actually log in and operate it.
 There is no session instance that is supposed to log in and operate
 Obviously, I wasn't sure what to do.
 As a result of various investigations, I managed to proceed, so I wrote it as a memo.
 It took a long time to solve it, so I hope it will be helpful for those who are having trouble with the same part.


## What is the MVC test referred to in this article?
 First of all, I will show you what the MVC test in this case is.
 Specify the URL and specify the HTTP method as shown below.
 The return value and other things that test various states.

```java
        @Test
        public void getTest() throws Exception {
    
            ResultActions resultActions =
                    mockMvc.perform(MockMvcRequestBuilders.get("/data/showList")                    
                            .with(user(mockAdminUser)))
                            .andExpect(MockMvcResultMatchers.status().is2xxSuccessful());
        }

Three main problems

The problem was mainly with 3 null pointers.

-① It becomes null because the session to be used in request.getSession.getAttribute is not set. -② @Autowired The instance is null in the part where HttpServletRequest is instantiated. --③ The variable defined in session bean is null when used in @Autowired.

The solution

About ①

This issue is when accessing the URL you are testing A null pointer was occurring when the following description was made in the logic.


    @Autowired
    HttpServletRequest request;
    
    public void f() {
        //request is null.null pointer Exception during getSession
        Object o = request.getSession.getAttribute("abc");
    }

This problem can be solved by setting as follows when testing You can now inject HttpServletRequest.


    HttpServletRequest request;
    
    request = new MockHttpServletRequest();
    RequestContextHolder.setRequestAttributes(new ServletWebRequest(request));

By describing the above at the time of setup such as @Before part at the time of test execution, the instance can be used in the call in the method to be tested. The offshore members assigned to the same project used it in other tests (not Mvc tests), so I used that as a reference.

I'm not good at English, but because it's as follows Semanticly "bind Request Attributes with the current threat" Does it mean

    /**
    	 * Bind the given RequestAttributes to the current thread,
    	 * <i>not</i> exposing it as inheritable for child threads.
    	 * @param attributes the RequestAttributes to expose
    	 * @see #setRequestAttributes(RequestAttributes, boolean)
    	 */
    	public static void setRequestAttributes(RequestAttributes attributes) {
    		setRequestAttributes(attributes, false);
    	}

About ②

The place where the null pointer occurs is as follows.

    @Autowired
    HttpServletRequest request;
    
    public void f() {
        MyClass o = (MyClass)request.getSession.getAttribute("abc");
        //Since the value is not set by setAttribute
        o.getName();//null pointer exception
    }

When there is such a description, the variable set in request is somewhere before entering this method It is assumed that it is setAttribute, but since the MVC test specifies a specific part, it will naturally be null if it is not set.

This creates an instance of MockHttpSession and Set and resolve when mvc.perform.

First, create an instance of MockHttpSessin by writing as follows. Methodize so that you can specify multiple session variables.


    public static MockHttpSession getMockHttpSession(Map<String, Object> sessions) {
            MockHttpSession mockHttpSession = new MockHttpSession();
            for (Map.Entry<String, Object> session: sessions.entrySet()) {
                mockHttpSession.setAttribute(session.getKey(), session.getValue());
            }
            return mockHttpSession;
        }

Since the argument is a map, use it as follows. Pass MockHttpSession together with mockMvc.perform.


    Map<String,Object> sessionMap =  new LinkedHashMap<String, Object>(){{
    	            put("id", 123);
    	            put("userName", "taro");
    	        }};
    MockHttpSession mockSession = 
            getMockHttpSession(sessionMap);
    
    
    ResultActions resultActions =
                    mockMvc.perform(MockMvcRequestBuilders.get("/data/showList")                    
                            .session(mockSession)
                            .with(user(mockAdminUser)))
                            .andExpect(MockMvcResultMatchers.status().is2xxSuccessful());

Now you have a value for the session variable in the method used during the test.

About ③

Set the variable specified as RequestScope as shown below It became a null pointer in the method that injected the variable.


    @Data//using lombok
    class MySession {
        MyClass me;
    }
   @Bean
   @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
   public MySession mySession(){
       MySession m = new MySession();
       return m;
   }
    

    @Autowired
    Mysession mySession
    
    
    public void calcService(){
        //null pointer exception
        mySession.getMe()
    }

This is also natural for the same reason as ②, but I was looking for a method because I could not set the value with method ②. I solved it by referring to this article. https://stackoverflow.com/questions/2411343/request-scoped-beans-in-spring-testing

Make the following class Describe to register WebApplicationContext.SCOPE_SESSION.


    public class WebContextTestExecutionListener extends AbstractTestExecutionListener {
    
        @Override
        public void prepareTestInstance(TestContext testContext) {
            if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
                GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
                ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
                beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,
                        new RequestScope());
                beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,
                        new SimpleThreadScope());
            }
        }
    }

Annotate this to the test class.

    
    @ActiveProfiles("test")
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = TestConfig.class)
    @WebIntegrationTest(randomPort = true)
    @TestExecutionListeners({WebContextTestExecutionListener.class,
            DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class})
    @Transactional
    public class MvcTestSample {

With this, if you set this variable at the time of testing In the method after accessing at mockMvc.perform There is a value when there is a description that refers to this variable.

		@Autowired
		Mysession mySession
        
    
        @Before
        public void setUp() throws Exception {
            MyClass me = new MyClass();
            mySession.setMe(me);   
        }

With the above, I was able to solve this problem for the time being and proceed with writing the test code. I think testing is absolutely necessary, but it is difficult to set up various things before testing.

I also referred to other articles for the solution to (2). I should link it as a courtesy, but I don't know where. I will link it as soon as I understand it.

that's all.

Recommended Posts

Resolve null pointers for various sessions during MVC testing of Spring boot.
WebMvcConfigurer Memorandum of Understanding for Spring Boot 2.0 (Spring 5)
Various correspondence table of Spring Framework and Spring Boot
Accelerate testing of Validators that require DI in Spring Boot
Introductory hands-on for beginners of Spring 5 & Spring Boot 2 has been released
Various switching application.properties for each environment when Spring Boot starts
Spring Boot for annotation learning