簡體   English   中英

當自動裝配的依賴項替換為接口或抽象類時,為什么這個 Spring Boot MVC 測試會失敗?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM