在 Spring 上模拟 @Validated 注释控制器方法的 ConstraintValidator
Mock a ConstraintValidator of a @Validated annotated controller method on Spring
使用 Spring Boot 1.3.6.RELEASE,我正在尝试使用 Junit、Mockito 和 MockMvc 对控制器方法进行单元测试。我已经构建了一个自定义约束验证器(扩展 ConstraintValidator)来自动装配服务。我的目标实体使用自定义验证器注释和一个组进行注释。我的 控制器方法签名 如下:
@RequestMapping ( value = "api/task/newtask", method = RequestMethod.POST )
public ResponseEntity submitTask ( <strong>@Validated ( value = TaskDependenciesDbValidation.class )</strong>
@RequestBody Task task )</pre>
我的自定义验证器的 isValid(..) 方法
@Override
public boolean <strong>isValid</strong> ( Ticket ticket, ConstraintValidatorContext context )
{
try
{
this.ticketService.verifyTicketExists( ticket.getId() );
return true;
} catch ( ResourceNotFoundException e )
{
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate( "Ticket with id " + ticket.getId() + " not found" )
.addConstraintViolation();
return false;
}
}</pre>
在运行时,一切正常。
我想通过完全模拟我的自定义约束验证器或只模拟验证器使用的 ticketService 来对我的控制器的 submitTask 方法进行单元测试。
我尝试用 Mockito.when(..) 模拟服务和验证器 "traditionally"(不是同时),但我在服务上收到 NullPointerException。
编辑:
测试尝试:
@RunWith ( SpringJUnit4ClassRunner.class )
@SpringApplicationConfiguration ( classes = MyApplication.class )
@ContextConfiguration ( classes = MockServletContext.class )
@WebAppConfiguration
public class TaskControllerTests
{
@InjectMocks
TaskController taskController;
@Mock
TaskService taskService;
@Mock
TicketService ticketService;
.
.
@Before
public void setup ()
{
MockitoAnnotations.initMocks( this );
mockMvc = standaloneSetup( this.taskController )
.setControllerAdvice( new RestExceptionHandler() )
.build();
}
.
.
@Test
public void submitTask () throws Exception
{
when( taskService.create( any( Task.class ) ) ).thenReturn( this.task );
doNothing().when( ticketService ).verifyTicketExists( 1L );
mockMvc.perform( post( "/api/task/newtask" ).content( "..validpayload..") ).andExpect( status().isCreated() );
}
}
您可以使用 JMockit's @Mocked annotation:
完全模拟验证器
public class Test {
@Mocked // mocks the class everywhere
TaskDependenciesDbConstraintValidator validator;
@Test
public void testMethod(){
new Expectations {{ // expect the validator to be called
validator.isValid((Ticket) any, (ConstraintValidatorContext) any);
result = true; // and declare the object to be valid
}}
// do your testing here
}
}
至于您的示例,如果您正在对控制器进行单元测试,为什么要调出整个 Spring 上下文?因为这就是你用
做的
@RunWith ( SpringJUnit4ClassRunner.class )
@SpringApplicationConfiguration ( classes = MyApplication.class )
@ContextConfiguration ( classes = MockServletContext.class )
@WebAppConfiguration
首先,您应该删除这些并重试。
我通过 tomasz_kusmierczyk
关注 this post 来实现它
可以找到我创建的约束验证工厂here and the creation of the "test validator" can be found here。
我的自定义验证器(TicketExistsValidator
和 UsersExistValidator
)在内部使用服务(分别为 TicketService
和 UserService
)。我在测试中创建了这些服务的模拟,然后将模拟服务传递给工厂(然后工厂使用验证器的 setters 来设置模拟服务)。
之后,可以使用 mockito.when() 来模拟服务。
为了使注释约束(@NotNull、@Size)在测试中起作用,我必须同时使用@Valid 和@Validated(CustomGroup.java) 注释我的控制器。
这是一个老问题,但我认为我会提供一个对我有用并且非常直接的答案。不确定第一次问问题时是否可用。
@RunWith(SpringRunner.class)
@SpringBootTest(classes= {ValidationAutoConfiguration.class})
public class AllowedValuesValidatorTest {
@Autowired
private Validator validator;
@Test
public void testIsValid() {
ObjectToBeValidated obj = // create object
Set<ConstraintViolation<ObjectToBeValidated>> violations = validator.validate(obj);
boolean violationsFound =
violations.stream().anyMatch(v -> v.getConstraintDescriptor().getAnnotation().annotationType().equals(
NonNullLowercaseLettersOrNumbersOnly.class));
assertThat(externalIdViolationFound).isTrue();
}
}
ValidationAutoConfiguration.class 作为测试的配置完成了繁重的工作。这将在 ObjectToBeValidated 上执行所有验证,您可以搜索您正在测试的违规行为。
使用 Spring Boot 1.3.6.RELEASE,我正在尝试使用 Junit、Mockito 和 MockMvc 对控制器方法进行单元测试。我已经构建了一个自定义约束验证器(扩展 ConstraintValidator)来自动装配服务。我的目标实体使用自定义验证器注释和一个组进行注释。我的 控制器方法签名 如下:
@RequestMapping ( value = "api/task/newtask", method = RequestMethod.POST ) public ResponseEntity submitTask ( <strong>@Validated ( value = TaskDependenciesDbValidation.class )</strong> @RequestBody Task task )</pre>
我的自定义验证器的 isValid(..) 方法@Override public boolean <strong>isValid</strong> ( Ticket ticket, ConstraintValidatorContext context ) { try { this.ticketService.verifyTicketExists( ticket.getId() ); return true; } catch ( ResourceNotFoundException e ) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate( "Ticket with id " + ticket.getId() + " not found" ) .addConstraintViolation(); return false; } }</pre>
在运行时,一切正常。
我想通过完全模拟我的自定义约束验证器或只模拟验证器使用的 ticketService 来对我的控制器的 submitTask 方法进行单元测试。
我尝试用 Mockito.when(..) 模拟服务和验证器 "traditionally"(不是同时),但我在服务上收到 NullPointerException。
编辑:
测试尝试:
@RunWith ( SpringJUnit4ClassRunner.class ) @SpringApplicationConfiguration ( classes = MyApplication.class ) @ContextConfiguration ( classes = MockServletContext.class ) @WebAppConfiguration public class TaskControllerTests { @InjectMocks TaskController taskController; @Mock TaskService taskService; @Mock TicketService ticketService; . . @Before public void setup () { MockitoAnnotations.initMocks( this ); mockMvc = standaloneSetup( this.taskController ) .setControllerAdvice( new RestExceptionHandler() ) .build(); } . . @Test public void submitTask () throws Exception { when( taskService.create( any( Task.class ) ) ).thenReturn( this.task ); doNothing().when( ticketService ).verifyTicketExists( 1L ); mockMvc.perform( post( "/api/task/newtask" ).content( "..validpayload..") ).andExpect( status().isCreated() ); } }
您可以使用 JMockit's @Mocked annotation:
完全模拟验证器public class Test {
@Mocked // mocks the class everywhere
TaskDependenciesDbConstraintValidator validator;
@Test
public void testMethod(){
new Expectations {{ // expect the validator to be called
validator.isValid((Ticket) any, (ConstraintValidatorContext) any);
result = true; // and declare the object to be valid
}}
// do your testing here
}
}
至于您的示例,如果您正在对控制器进行单元测试,为什么要调出整个 Spring 上下文?因为这就是你用
做的@RunWith ( SpringJUnit4ClassRunner.class )
@SpringApplicationConfiguration ( classes = MyApplication.class )
@ContextConfiguration ( classes = MockServletContext.class )
@WebAppConfiguration
首先,您应该删除这些并重试。
我通过 tomasz_kusmierczyk
关注 this post 来实现它可以找到我创建的约束验证工厂here and the creation of the "test validator" can be found here。
我的自定义验证器(TicketExistsValidator
和 UsersExistValidator
)在内部使用服务(分别为 TicketService
和 UserService
)。我在测试中创建了这些服务的模拟,然后将模拟服务传递给工厂(然后工厂使用验证器的 setters 来设置模拟服务)。
之后,可以使用 mockito.when() 来模拟服务。
为了使注释约束(@NotNull、@Size)在测试中起作用,我必须同时使用@Valid 和@Validated(CustomGroup.java) 注释我的控制器。
这是一个老问题,但我认为我会提供一个对我有用并且非常直接的答案。不确定第一次问问题时是否可用。
@RunWith(SpringRunner.class)
@SpringBootTest(classes= {ValidationAutoConfiguration.class})
public class AllowedValuesValidatorTest {
@Autowired
private Validator validator;
@Test
public void testIsValid() {
ObjectToBeValidated obj = // create object
Set<ConstraintViolation<ObjectToBeValidated>> violations = validator.validate(obj);
boolean violationsFound =
violations.stream().anyMatch(v -> v.getConstraintDescriptor().getAnnotation().annotationType().equals(
NonNullLowercaseLettersOrNumbersOnly.class));
assertThat(externalIdViolationFound).isTrue();
}
}
ValidationAutoConfiguration.class 作为测试的配置完成了繁重的工作。这将在 ObjectToBeValidated 上执行所有验证,您可以搜索您正在测试的违规行为。