简体   繁体   中英

Why does this Spring Boot MVC test fail when autowired dependency replaced with interface or abstract class?

This is the Controller as it appears at the moment.

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

Here is the Controller Advice to handle exceptions

@ExceptionHandler(HttpStatusCodeException.class)
public ModelAndView handleException(HttpServletRequest req, HttpStatusCodeException ex) {
    log.error(ex.getMessage(), ex);
    ModelAndView mav = new ModelAndView();
    // do stuff
    return mav;
}

And this is the current test, which works fine


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

The test passes successfully in that the mock throws the HttpClientErrorException , which the advice catches and then redirects the user to a logout page.

But when I replace the MyService member in the MyController class with a class that implements an interface, the test fails because the HttpClientErrorException is no longer being caught by the advice and instead is being thrown as a nested exception.

Here is the new Controller

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

Here is the interface implementation

@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();
    }
}

And here is the new test


@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"
    }
}

Here is the failing test stack trace

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>

Any help is appreciated

Problem solved and, as with about 90% of these kinds of problems, the error was simple to fix. In my test class, I simply hadn't assigned the exception handler class to the 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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM