为什么当自动连线依赖项替换为接口或抽象类时,此 Spring 启动 MVC 测试会失败?



这是当前出现的控制器。

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;
}
}

相关内容

  • 没有找到相关文章

最新更新