簡體   English   中英

為什么 MockMvc 在啟用 Spring Security 的有效路徑上返回 404 錯誤?

[英]Why does MockMvc in return 404 error on valid path with Spring Security enabled?

我正在嘗試使用 MockMvc 測試我的 Spring Boot REST 控制器。 以下是我的測試課:

UserControllerImplTest(版本 1)

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerImplTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserDTOService userDTOService;

    @MockBean
    private ImageDTOService imageDTOService;

    @Test
    public void whenUsers_shouldReturnUsers() throws Exception {

        UserDTO user1 = getSampleUser("user1");
        UserDTO user2 = getSampleUser("user2");
        UserDTO user3 = getSampleUser("user3");
        List<UserDTO> users = List.of(user1, user2, user3);

        Mockito.when(userDTOService.getAll()).thenReturn(users);

        mockMvc.perform(get("/user"))
                .andExpect(status().isOk())
                .andExpect(content().string(users.toString()));
    }

    private UserDTO getSampleUser(String username) {
        return UserDTO.builder()
                .username(username)
                .email(username + "@example.com")
                .password("password")
                .registerTime(LocalDateTime.now())
                .isActive(true)
                .build();
    }
}

待測控制器: UserController:

@Api(tags={"User info"})
@RequestMapping("/user")
public interface UserController {

    @ApiOperation(value = "Returns list of users")
    @GetMapping("/")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    ResponseEntity<List<UserDTO>> users();

    @ApiOperation(value = "Returns single user")
    @GetMapping("/{username}")
    ResponseEntity<UserDTO> userInfo(@PathVariable String username);

    @ApiOperation(value = "Returns list of images uploaded by user authenticated with session cookie")
    @GetMapping("/images")
    @PreAuthorize("isFullyAuthenticated()")
    ResponseEntity<List<ImageDTO>> userImages(@AuthenticationPrincipal CustomUserDetails userDetails
    );
}

它的實現: UserControllerImpl

@Service
@RequiredArgsConstructor
public class UserControllerImpl implements UserController {

    private final UserDTOService userDTOService;
    private final ImageDTOService imageDTOService;

    @Override
    public ResponseEntity<List<UserDTO>> users() {
        return ResponseEntity.status(HttpStatus.OK).body(List.of(UserDTO.builder().username("aaa").build()));
//        Optional<List<UserDTO>> users = Optional.ofNullable(userDTOService.getAll());
//        if (users.isEmpty()) {
//            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
//        }
//
//        return ResponseEntity.status(HttpStatus.OK).body(users.get());
    }

    @Override
    public ResponseEntity<UserDTO> userInfo(String username) {
        Optional<UserDTO> requestedUser = Optional.ofNullable(userDTOService.findByUsername(username));
        if (requestedUser.isEmpty()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }

        return ResponseEntity.status(HttpStatus.OK).body(requestedUser.get());
    }

    @Override
    public ResponseEntity<List<ImageDTO>> userImages(@AuthenticationPrincipal CustomUserDetails userDetails) {
        UserDTO user = userDetails.getUser();

        Optional<List<ImageDTO>> images = Optional.ofNullable(imageDTOService.findAllUploadedBy(user));
        if (images.isEmpty()) {
            return ResponseEntity.status(HttpStatus.OK).body(Collections.emptyList());
        }

        return ResponseEntity.status(HttpStatus.OK).body(images.get());
    }
}

我在控制器中調用的方法( users() )被修改了,只是為了確保它總是返回 200。

測試失敗的原因是:

java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual   :404

我強烈懷疑我定義的 Filter Bean 可能是這里的罪魁禍首。

AuthCookieFilter

@RequiredArgsConstructor
@Component
public class AuthCookieFilter extends GenericFilterBean {

    private final SessionDTOService sessionDTOService;
    private final AppConfig appConfig;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        Optional<String> sessionId = Optional.ofNullable(extractAuthCookie((HttpServletRequest) servletRequest));

        if (sessionId.isPresent()) {
            Optional<SessionDTO> sessionDTO = Optional.ofNullable(sessionDTOService.findByIdentifier(sessionId.get()));

            if (sessionDTO.isPresent()) {
                UserDTO userDTO = sessionDTO.get().getUser();
                CustomUserDetails customUserDetails = new CustomUserDetails(userDTO);

                SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(customUserDetails));
            }
        }

        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", appConfig.getCorsHosts());
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Origin, Authorization, Content-Type, Cache-Control");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        filterChain.doFilter(servletRequest, response);
    }

    public static String extractAuthCookie(HttpServletRequest request) {
        List<Cookie> cookies = Arrays.asList(Optional.ofNullable(request.getCookies()).orElse(new Cookie[0]));

        if (!cookies.isEmpty()) {
            Optional<Cookie> authCookie = cookies.stream()
                    .filter(cookie -> cookie.getName().equals("authentication"))
                    .findFirst();

            if (authCookie.isPresent()) {
                return authCookie.get().getValue();
            }
        }

        return null;
    }
}

所以我決定手動配置 MockMvc,將其顯式添加到上下文中: UserControllerImplTest (version 2)

@SpringBootTest
@WebAppConfiguration
class UserControllerImplTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private AuthCookieFilter authCookieFilter;

    @MockBean
    private UserDTOService userDTOService;

    @MockBean
    private ImageDTOService imageDTOService;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                .addFilter(authCookieFilter).build();
    }

    @Test
    public void whenUsers_shouldReturnUsers() throws Exception {

        UserDTO user1 = getSampleUser("user1");
        UserDTO user2 = getSampleUser("user2");
        UserDTO user3 = getSampleUser("user3");
        List<UserDTO> users = List.of(user1, user2, user3);

        Mockito.when(userDTOService.getAll()).thenReturn(users);

        mockMvc.perform(get("/user"))
                .andExpect(status().isOk())
                .andExpect(content().string(users.toString()));
    }

    private UserDTO getSampleUser(String username) {
        return UserDTO.builder()
                .username(username)
                .email(username + "@example.com")
                .password("password")
                .registerTime(LocalDateTime.now())
                .isActive(true)
                .build();
    }
}

但沒有成功,仍然得到404。

我還考慮通過使用@WebMvcTest將測試范圍限制為僅顯示 Web 層,但如上所示,我的AuthCookieFilter需要完整的 Spring 上下文。

我注意到,當我在那里放置斷點時,調試器會在AuthCookieFilterdoFilter內停止,而不管 MockMvc 是自動配置還是手動配置。 但是我無法到達UserControllerImpl.users()內設置的斷點。

此應用程序中啟用了 Web 安全性,並且正在添加額外的過濾器: SecurityConfiguration.class

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final CustomUserDetailsService userDetailsService;

    private final CustomLogoutSuccessHandler customLogoutSuccessHandler;

    private final AuthCookieFilter authCookieFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(getPasswordEncoder());
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() {
        return authentication -> {
            throw new AuthenticationServiceException("Cannot authenticate " + authentication);
        };
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .csrf().disable()
                .logout(configurer -> {
                    configurer.addLogoutHandler(new HeaderWriterLogoutHandler(
                        new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL)
                    ));
                    configurer.logoutSuccessHandler(customLogoutSuccessHandler);
                    configurer.logoutUrl("/auth/logout");
                    configurer.deleteCookies("authentication");
                })
                .exceptionHandling(configurer -> configurer.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
                .addFilterAfter(authCookieFilter, SecurityContextPersistenceFilter.class)
                .authorizeRequests()
                .antMatchers("/auth/*").permitAll()
                .and()
                .formLogin().permitAll()
        ;
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        String defaultEncodingId = "argon2";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(defaultEncodingId, new Argon2PasswordEncoder(16, 32, 8, 1 << 16, 4));
        return new DelegatingPasswordEncoder(defaultEncodingId, encoders);
    }
}

以下是應用程序啟動和測試執行的日志:

21:40:56.203 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
21:40:56.210 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
21:40:56.228 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
21:40:56.237 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest], using SpringBootContextLoader
21:40:56.240 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: class path resource [com/ewsie/allpic/user/controller/impl/UserControllerImplTest-context.xml] does not exist
21:40:56.240 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: class path resource [com/ewsie/allpic/user/controller/impl/UserControllerImplTestContext.groovy] does not exist
21:40:56.240 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: no resource found for suffixes {-context.xml, Context.groovy}.
21:40:56.241 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: UserControllerImplTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
21:40:56.276 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]
21:40:56.320 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/home/max/Projects/allpic/allpic-backend/target/classes/com/ewsie/allpic/AllpicApplication.class]
21:40:56.321 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.ewsie.allpic.AllpicApplication for test class com.ewsie.allpic.user.controller.impl.UserControllerImplTest
21:40:56.382 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: using defaults.
21:40:56.382 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener, org.springframework.security.test.context.support.ReactorContextTestExecutionListener]
21:40:56.391 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@7e22550a, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@45e37a7e, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@62452cc9, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6941827a, org.springframework.test.context.support.DirtiesContextTestExecutionListener@5a7005d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@5bc9ba1d, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@1021f6c9, org.springframework.test.context.event.EventPublishingTestExecutionListener@7516e4e5, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener@488eb7f2, org.springframework.security.test.context.support.ReactorContextTestExecutionListener@5e81e5ac, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@4189d70b, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@3fa2213, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@3e7634b9, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@6f0b0a5e, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@6035b93b]
21:40:56.393 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@2a8d39c4 testClass = UserControllerImplTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@25b2cfcb testClass = UserControllerImplTest, locations = '{}', classes = '{class com.ewsie.allpic.AllpicApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@72758afa key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@30b6ffe0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@14f232c4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@d324b662, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@f627d13, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@479cbee5], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
21:40:56.409 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=-1}
2020-09-13 21:40:56.599  INFO 52850 --- [           main] c.e.a.u.c.impl.UserControllerImplTest    : Starting UserControllerImplTest on Oyashiro-sama with PID 52850 (started by max in /home/max/Projects/allpic/allpic-backend)
2020-09-13 21:40:56.599  INFO 52850 --- [           main] c.e.a.u.c.impl.UserControllerImplTest    : No active profile set, falling back to default profiles: default
2020-09-13 21:40:57.143  INFO 52850 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-09-13 21:40:57.197  INFO 52850 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 49ms. Found 5 JPA repository interfaces.
2020-09-13 21:40:57.716  INFO 52850 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration' of type [org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.722  INFO 52850 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'objectPostProcessor' of type [org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.723  INFO 52850 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@1de08775' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.727  INFO 52850 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration' of type [org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.731  INFO 52850 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.872  INFO 52850 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-09-13 21:40:57.909  INFO 52850 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.12.Final
2020-09-13 21:40:57.931  INFO 52850 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-09-13 21:40:57.993  INFO 52850 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-09-13 21:40:58.098  INFO 52850 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-09-13 21:40:58.108  INFO 52850 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
(sql queries here)
2020-09-13 21:40:58.523  INFO 52850 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-09-13 21:40:58.527  INFO 52850 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-09-13 21:40:59.025  WARN 52850 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-09-13 21:40:59.661  INFO 52850 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@647bd553, org.springframework.security.web.context.SecurityContextPersistenceFilter@16d7f503, com.ewsie.allpic.user.security.AuthCookieFilter@a47a011, org.springframework.security.web.header.HeaderWriterFilter@1b9785aa, org.springframework.security.web.authentication.logout.LogoutFilter@2ddb260e, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4ceac22d, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1203c259, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@15311af2, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@54602d5a, org.springframework.security.web.session.SessionManagementFilter@347074a9, org.springframework.security.web.access.ExceptionTranslationFilter@2eebb07, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@441a2d52]
2020-09-13 21:40:59.901  INFO 52850 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-09-13 21:41:00.036  INFO 52850 --- [           main] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:default'
2020-09-13 21:41:00.070  INFO 52850 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2020-09-13 21:41:00.111  INFO 52850 --- [           main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2020-09-13 21:41:00.111  INFO 52850 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2020-09-13 21:41:00.118  INFO 52850 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 7 ms
2020-09-13 21:41:00.428  INFO 52850 --- [           main] c.e.a.u.c.impl.UserControllerImplTest    : Started UserControllerImplTest in 4.013 seconds (JVM running for 4.7)

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils (file:/home/max/.m2/repository/org/springframework/spring-core/5.2.5.RELEASE/spring-core-5.2.5.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /user
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 404
    Error message = null
          Headers = [Access-Control-Allow-Origin:"http://localhost:4200", Access-Control-Allow-Methods:"POST, PUT, GET, OPTIONS, DELETE", Access-Control-Max-Age:"3600", Access-Control-Allow-Headers:"Access-Control-Allow-Origin, Authorization, Content-Type, Cache-Control", Access-Control-Allow-Credentials:"true", Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []


java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual   :404

如何使 MockMvc 工作並模擬我的控制器?

問題是/user確實無處可尋,所以 404 響應是完全合理的。

更改后:

mockMvc.perform(get("/user"))

至:

mockMvc.perform(get("/user/")) (注意尾隨/

我能夠收到實際響應並繼續進行測試。

就我而言,我使用@Import手動導入了控制器類,它工作正常。 似乎@WebMvcTest沒有導入控制器 bean。 見下面的代碼:

@ContextConfiguration(
    classes = [TestConfigs::class]
)
@WebMvcTest
@Import(MyController::class)
class MyControllerTests {

    @Test
    fun testPost() {
        //test logic goes here
    }
}

請注意我是如何使用@Import的: @Import(MyController::class)

如果我刪除@Import ,我會得到 404。

希望能幫助到你

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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