網頁

2020/10/15

Spring Boot Test net.bull.javamelody MockMvc.perform() NullPointerException

今天在寫Spring Boot的Controller測試時,執行MockMvc.perform()總是發生NullPointerException如下。

問題環境:

  • Spring Boot 2.1.x

java.lang.NullPointerException
    at net.bull.javamelody.MonitoringFilter.doFilter(MonitoringFilter.java:209)
    at org.springframework.test.web.servlet.setup.PatternMappingFilterProxy.doFilter(PatternMappingFilterProxy.java:101)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:182)
    at com.abc.demo.controller.DemoControllerTests.test(DemoControllerTests.java:20)
    ...

最後發現原來是JavaMelody這套Java資源監控套件的問題,在pom.xml的依賴如下。

pom.xml

<dependency>
    <groupId>net.bull.javamelody</groupId>
    <artifactId>javamelody-spring-boot-starter</artifactId>
    <version>${version}</version>
</dependency>

原因出在@SpringBootTest測試預設是Mock的web環境,server並不啟動;而JavaMelody插入的MonitoringFilter在執行時因為少了真實web環境的內容因此導致屬性MonitoringFilter.httpCounternull,進而導致呼叫其方法時發生NullPointerException

MonitoringFilter

public class MonitoringFilter implements Filter {
    ...
    private Counter httpCounter;
    ...
    @Override
    public void init(FilterConfig config) throws ServletException {
        ...
        this.httpCounter = collector.getCounterByName(Counter.HTTP_COUNTER_NAME); // return null;
        ...
    }
    ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        ...
        if (!httpCounter.isDisplayed() || isRequestExcluded((HttpServletRequest) request)) { // NullPointerException!!
            ...
        }
        ...
    }
    ...
}

解決方法一是在Mock環境測試時排除JavaMelody的配置,可在application.properties設定spring.autoconfigure.exclude=net.bull.javamelody.JavaMelodyAutoConfiguration

application.properites

spring.autoconfigure.exclude=net.bull.javamelody.JavaMelodyAutoConfiguration

解決方法二是測試時使用真實的web環境。

package com.abc.demo.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // <--改用真實的web環境
class DemoControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void test() throws Exception {
        mockMvc.perform(get("/test"))
                .andExpect(status().isOk());
    }

}

解決方法三是改用MockMvcBuilders.webAppContextSetup()並注入WebApplicationContext取得有Web環境資訊的MockMvc

package com.abc.demo.controller;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest
class DemoControllerTests {

//    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @BeforeEach
    void beforeEach() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    void test() throws Exception {
        mockMvc.perform(get("/test"))
                .andExpect(status().isOk());
    }

}

這問題花了我兩個小時。

除了JavaMelody,其他有用到filter的套件可能都會發生類似的問題。


沒有留言:

張貼留言