![](/img/trans.png)
[英]Mock an autowired spring dependency (Interface) which is not directly used by the test class
[英]Why does this Spring Boot MVC test fail when autowired dependency replaced with interface or abstract class?
這是目前出現的控制器。
import com.myorg.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping(value = "/my/homepage")
public String someMethod(ModelMap model, Authentication authentication) {
myService.doSomething(authentication);
return "homepage";
}
}
這是處理異常的控制器建議
@ExceptionHandler(HttpStatusCodeException.class)
public ModelAndView handleException(HttpServletRequest req, HttpStatusCodeException ex) {
log.error(ex.getMessage(), ex);
ModelAndView mav = new ModelAndView();
// do stuff
return mav;
}
這是當前的測試,效果很好
import com.myorg.service.MyService
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
import spock.lang.Specification
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@SpringBootTest
class MyControllerSpec extends Specification {
@Autowired
private WebApplicationContext webApplicationContext
@SpringBean
private MyService myService = Mock()
@WithMockUser(username = "test@email.com", roles = ["USER"])
def "MyController(test=should not authenticate)"() {
setup:
def notAuthorizedException = new HttpClientErrorException(HttpStatus.UNAUTHORIZED, "blah", null, Charset.defaultCharset())
MockMvc mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build()
myService.doSomething(_) >> { throw notAuthorizedException }
when:
MvcResult mvcResult = mvc.perform(get("/my/homepage"))
.andExpect(status().is3xxRedirection())
.andReturn()
then:
mvcResult.response.status == 302
mvcResult.modelAndView.viewName == "redirect:/my/login?logout"
}
}
測試成功通過,因為模擬拋出HttpClientErrorException
,建議捕獲該異常,然后將用戶重定向到注銷頁面。
但是,當我用實現接口的類替換MyController
類中的MyService
成員時,測試失敗,因為HttpClientErrorException
不再被建議捕獲,而是作為嵌套異常被拋出。
這是新的控制器
import com.myorg.service.MyServiceThatImplementsAnInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
private final MyInterface myInterface;
@Autowired
public MyController(MyInterface myInterface) {
this.myInterface = myInterface;
}
@GetMapping(value = "/my/homepage")
public String someMethod(ModelMap model, Authentication authentication) {
myInterface.doSomething(authentication);
return "homepage";
}
}
下面是接口實現
@Component("myInterface")
public class MyServiceThatImplementsAnInterfaceImpl implements MyInterface {
private final SomeComponent someComponent;
public MyServiceThatImplementsAnInterfaceImpl(SomeComponent comeComponent) {
this.someComponent = someComponent;
}
@Override
public void doSomething(Authentication authenticationToken) {
someComponent.doSomethingElse();
}
}
這是新的測試
@SpringBootTest
class MyControllerSpec extends Specification {
@Autowired
private WebApplicationContext webApplicationContext
@SpringBean
private MyServiceThatImplementsAnInterfaceImpl myServiceThatImplementsAnInterface = Mock()
@WithMockUser(username = "test@email.com", roles = ["USER"])
def "MyController(test=should not authenticate)"() {
setup:
def notAuthorizedException = new HttpClientErrorException(HttpStatus.UNAUTHORIZED, "blah", null, Charset.defaultCharset())
MockMvc mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build()
myServiceThatImplementsAnInterface.doSomething(_) >> { throw notAuthorizedException }
when:
MvcResult mvcResult = mvc.perform(get("/my/homepage"))
.andExpect(status().is3xxRedirection())
.andReturn()
then:
mvcResult.response.status == 302
mvcResult.modelAndView.viewName == "redirect:/my/login?logout"
}
}
這是失敗的測試堆棧跟蹤
Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at app//org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at app//org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at app//javax.servlet.http.HttpServlet.service(HttpServlet.java:497)
at app//org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at app//org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
at app//javax.servlet.http.HttpServlet.service(HttpServlet.java:584)
at app//org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at app//org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
at app//org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at app//org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:122)
at app//org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:116)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
at app//org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:109)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:219)
at app//org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at app//org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117)
at app//org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at app//org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at app//org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
at app//org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at app//org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at app//org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
at app//org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
at app//org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
at app//org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurer$DelegateFilter.doFilter(SecurityMockMvcConfigurer.java:132)
at app//org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at app//org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:201)
at com.myorg.controller.MyControllerSpec.<where my test is>
Caused by: org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at com.myorg.controller.MyControllerSpec.<where my test is>
任何幫助表示贊賞
問題已解決,並且與大約 90% 的此類問題一樣,錯誤很容易修復。 在我的測試類中,我只是沒有將異常處理程序類分配給 MockMvc。
def "MyController(test=should not authenticate)"() {
setup:
Authentication authentication = //Just mock out an Authentication object here
def notAuthorizedException = //As before, just mock out an exception
myInterface.doSomething(_) >> { throw notAuthorizedException }
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver()
viewResolver.setPrefix("prefix")
MyController myController = new MyController(myServiceThatImplementsAnInterface)
MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(myController)
.setControllerAdvice(new MyExceptionHandler())
.setViewResolvers(viewResolver)
.build()
when:
MvcResult mvcResult = mockMvc.perform(get("/my/homepage")
.principal(authentication))
.andExpect(status().is3xxRedirection())
.andReturn()
then:
mvcResult.response.status == 302
mvcResult.modelAndView.viewName == "redirect:/app/login?logout"
}
@ControllerAdvice(assignableTypes = MyInterface.class)
public class MyExceptionHandler {
@ExceptionHandler(HttpStatusCodeException.class)
public ModelAndView handleException(HttpServletRequest req, HttpStatusCodeException ex) {
log.error(ex.getMessage(), ex);
ModelAndView mav = new ModelAndView();
// do stuff
return mav;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.