Mockito 和 JUnit 控制器在任何请求主体上测试 returns 400 json
Mockito and JUnit Controller test returns 400 on any request body json
我有一个到补丁数据的映射:
@Data
@NoArgsConstructor
public class CertificateUpdateDTO {
@InjectString
private String name;
@InjectString
private String description;
@Min(value = 0, message = "Price cannot be negative")
private double price;
@Min(value = 1, message = "Duration cannot be less than one day")
private int duration;
}
@ApiOperation("Update certificate by id")
@ApiResponses({
@ApiResponse(code = 200, message = "If updated successfully or certificate doesn't exist"),
@ApiResponse(code = 400, message = "If JSON object in request body is invalid"),
@ApiResponse(code = 404, message = "Certificate with given id doesn't exist")
})
@PatchMapping(value = "/{id}", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity update(@PathVariable int id, @Valid @DefaultDto CertificateUpdateDTO certificateUpdateDTO){
if(certificateService.updateCertificate(id, certificateUpdateDTO)){
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}
@DefaultDto
和 @InjectString
是我的注解,它们只是做与@RequestBody 相同的事情,并将默认值注入由 @InjectString
注释的字符串字段,它工作正常。
我需要检查是否请求此映射的 ID 1
returns 404,它从 curl:
开始工作
curl -X PATCH -H "Content-Type: application/json" -d '{"duration": "10", "price": "10"}' localhost:8080/v1/certificate/1
curl的响应码是404。
但是当我尝试 运行 测试它时 returns 400:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllerTestConfiguration.class, loader = AnnotationConfigContextLoader.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class CertificateControllerTest {
@Autowired
private CertificateService certificateService;
@Autowired
private CertificateController certificateController;
private ObjectMapper objectMapper = new ObjectMapper();
private MockMvc mockMvc;
@BeforeAll
public void init(){
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).build();
}
@AfterEach
public void postEach(){
reset(certificateService);
}
...
@Test
@SneakyThrows
public void updateFailTest(){
CertificateUpdateDTO certificateUpdateDTO = new CertificateUpdateDTO();
certificateUpdateDTO.setName("c1");
certificateUpdateDTO.setDescription("desk");
certificateUpdateDTO.setDuration(10);
certificateUpdateDTO.setPrice(10);
when(certificateService.updateCertificate(1, certificateUpdateDTO)).thenReturn(false);
mockMvc.perform(patch("/v1/certificate/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(certificateUpdateDTO))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().is(404));
verify(certificateService, times(1)).updateCertificate(1, certificateUpdateDTO);
}
}
当前测试class的配置是:
@Configuration
public class ControllerTestConfiguration {
...
@Bean
public CertificateService certificateService(){
return mock(CertificateService.class);
}
@Bean
public CertificateController certificateController(){
return new CertificateController(certificateService());
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
错误消息说持续时间无效:duration = 0
UPD:
我试图删除@Valid,发现我的注释在测试环境中不起作用,我是如何处理它们的:
public class DefaultValueHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(DefaultDto.class) != null;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
BufferedReader reader = servletRequest.getReader();
String body = reader.lines().collect(Collectors.joining());
Class<?> clazz = methodParameter.getParameterType();
Object dto = new ObjectMapper().readValue(body, clazz);
for(Field field : clazz.getDeclaredFields()){
InjectString annotation = field.getAnnotation(InjectString.class);
if(annotation != null){
field.setAccessible(true);
if(ReflectionUtils.getField(field, dto) == null) {
ReflectionUtils.setField(field, dto, annotation.value());
}
}
}
return dto;
}
}
以及我如何注册 MethodResolver:
public class AppConfig implements WebMvcConfigurer {
private final Environment env;
@Autowired
public AppConfig(Environment env) {
this.env = env;
}
...
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new DefaultValueHandlerMethodArgumentResolver());
}
...
}
经过一些研究和我的 leed fiend 的帮助,我找到了解决方案:)
要添加自定义 ArgumentResolver,您应该在 MockMvcBuilder 中注册它。
改变这个:
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).build();
为此:
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).setCustomArgumentResolvers(new DefaultValueHandlerMethodArgumentResolver()).build();
我有一个到补丁数据的映射:
@Data
@NoArgsConstructor
public class CertificateUpdateDTO {
@InjectString
private String name;
@InjectString
private String description;
@Min(value = 0, message = "Price cannot be negative")
private double price;
@Min(value = 1, message = "Duration cannot be less than one day")
private int duration;
}
@ApiOperation("Update certificate by id")
@ApiResponses({
@ApiResponse(code = 200, message = "If updated successfully or certificate doesn't exist"),
@ApiResponse(code = 400, message = "If JSON object in request body is invalid"),
@ApiResponse(code = 404, message = "Certificate with given id doesn't exist")
})
@PatchMapping(value = "/{id}", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity update(@PathVariable int id, @Valid @DefaultDto CertificateUpdateDTO certificateUpdateDTO){
if(certificateService.updateCertificate(id, certificateUpdateDTO)){
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}
@DefaultDto
和 @InjectString
是我的注解,它们只是做与@RequestBody 相同的事情,并将默认值注入由 @InjectString
注释的字符串字段,它工作正常。
我需要检查是否请求此映射的 ID 1
returns 404,它从 curl:
curl -X PATCH -H "Content-Type: application/json" -d '{"duration": "10", "price": "10"}' localhost:8080/v1/certificate/1
curl的响应码是404。 但是当我尝试 运行 测试它时 returns 400:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllerTestConfiguration.class, loader = AnnotationConfigContextLoader.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class CertificateControllerTest {
@Autowired
private CertificateService certificateService;
@Autowired
private CertificateController certificateController;
private ObjectMapper objectMapper = new ObjectMapper();
private MockMvc mockMvc;
@BeforeAll
public void init(){
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).build();
}
@AfterEach
public void postEach(){
reset(certificateService);
}
...
@Test
@SneakyThrows
public void updateFailTest(){
CertificateUpdateDTO certificateUpdateDTO = new CertificateUpdateDTO();
certificateUpdateDTO.setName("c1");
certificateUpdateDTO.setDescription("desk");
certificateUpdateDTO.setDuration(10);
certificateUpdateDTO.setPrice(10);
when(certificateService.updateCertificate(1, certificateUpdateDTO)).thenReturn(false);
mockMvc.perform(patch("/v1/certificate/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(certificateUpdateDTO))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().is(404));
verify(certificateService, times(1)).updateCertificate(1, certificateUpdateDTO);
}
}
当前测试class的配置是:
@Configuration
public class ControllerTestConfiguration {
...
@Bean
public CertificateService certificateService(){
return mock(CertificateService.class);
}
@Bean
public CertificateController certificateController(){
return new CertificateController(certificateService());
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
错误消息说持续时间无效:duration = 0
UPD:
我试图删除@Valid,发现我的注释在测试环境中不起作用,我是如何处理它们的:
public class DefaultValueHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(DefaultDto.class) != null;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
BufferedReader reader = servletRequest.getReader();
String body = reader.lines().collect(Collectors.joining());
Class<?> clazz = methodParameter.getParameterType();
Object dto = new ObjectMapper().readValue(body, clazz);
for(Field field : clazz.getDeclaredFields()){
InjectString annotation = field.getAnnotation(InjectString.class);
if(annotation != null){
field.setAccessible(true);
if(ReflectionUtils.getField(field, dto) == null) {
ReflectionUtils.setField(field, dto, annotation.value());
}
}
}
return dto;
}
}
以及我如何注册 MethodResolver:
public class AppConfig implements WebMvcConfigurer {
private final Environment env;
@Autowired
public AppConfig(Environment env) {
this.env = env;
}
...
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new DefaultValueHandlerMethodArgumentResolver());
}
...
}
经过一些研究和我的 leed fiend 的帮助,我找到了解决方案:)
要添加自定义 ArgumentResolver,您应该在 MockMvcBuilder 中注册它。
改变这个:
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).build();
为此:
mockMvc = MockMvcBuilders.standaloneSetup(certificateController).setCustomArgumentResolvers(new DefaultValueHandlerMethodArgumentResolver()).build();