英文:
Why does MockMvc in return 404 error on valid path with Spring Security enabled?
问题
Sure, here is the translated content:
@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();
}
}
Please note that the code has been translated as per your request, but I'm not sure whether the code itself is free of issues or will execute successfully. If you encounter any errors or issues, feel free to ask for assistance.
英文:
I'm trying to test my Spring Boot REST Controller with MockMvc. Following is my test class:
UserControllerImplTest (version 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();
}
}
Controller to be tested:
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
);
}
its implementation:
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());
}
}
The method I'm invoked in the controller (users()
) was modified, just to be sure it'll always return 200.
The test fails because of:
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
I had a strong suspicion that the Filter Bean I defined might be a culprit here.
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;
}
}
So I decided to configure MockMvc manually, explicitly adding it to the context:
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();
}
}
But had no success, still getting 404s.
I also considered limiting the test scope to bringing up only the Web layer, by using @WebMvcTest
, but my AuthCookieFilter
, as you can see above, requires full Spring context to be up.
I noticed, that debugger stops inside doFilter
of AuthCookieFilter
when I put a breakpoint there, disregarding whether MockMvc is configured automatically or manually. However I cannot reach a breakpoint set inside UserControllerImpl.users()
.
Web security is enabled in this app and that's were additional filter is being added:
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);
}
}
Here are logs of the application start-up and test execution:
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
How can I make the MockMvc work and mock my controller?
答案1
得分: 3
问题在于 /user
确实无处可寻,因此 404 响应是完全合理的。
在更改后:
mockMvc.perform(get("/user"))
为:
mockMvc.perform(get("/user/"))
(注意末尾的 /
)
我能够收到实际的响应并继续进行测试。
英文:
The problem was that /user
was indeed nowhere to be found, so 404 response was completely justified.
After chaniging:
mockMvc.perform(get("/user"))
to:
mockMvc.perform(get("/user/"))
(note the trailing /
)
I was able to recieve the actual response and carry on with the test.
答案2
得分: 2
在我的情况下,我使用 @Import
手动导入了控制器类,并且它起作用了。似乎控制器 Bean 没有被 @WebMvcTest
导入。请看下面的代码:
@ContextConfiguration(
classes = [TestConfigs::class]
)
@WebMvcTest
@Import(MyController::class)
class MyControllerTests {
@Test
fun testPost() {
// 测试逻辑放在这里
}
}
请注意我如何使用 @Import
:@Import(MyController::class)
。
如果我移除 @Import
,我会得到 404 错误。
希望有所帮助。
英文:
In my case, I imported the controller class manually using @Import
and it worked. It seems the controller bean had not been imported by @WebMvcTest
. See below code :
@ContextConfiguration(
classes = [TestConfigs::class]
)
@WebMvcTest
@Import(MyController::class)
class MyControllerTests {
@Test
fun testPost() {
//test logic goes here
}
}
Note that how I used @Import
: @Import(MyController::class)
.
If I remove @Import
, I would get 404.
Hope it helps
答案3
得分: 1
尝试将控制器中的接口移除。
在我的情况下,只有这个操作会导致404错误。如果我在控制器中添加任何接口定义,测试中就会出现404错误,但在工作模式下我的控制器正常工作。我仍在调查这个问题,我认为测试框架不能正确地使用接口定义注册控制器。
已解决:
@Import(AopAutoConfiguration.class)
https://stackoverflow.com/a/56504756/20816770
英文:
Try to remove interface from controller.
In my case only this cause 404 error, if I add any interface definition to my controller, I get 404 error in tests, but in work mode my controller working fine. I still investigating this issue, I think test framework cannot register controller correcly with interface definition.
Resolved:
@Import(AopAutoConfiguration.class)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论