今天在寫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.httpCounter
為null
,進而導致呼叫其方法時發生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的套件可能都會發生類似的問題。
沒有留言:
張貼留言