在Spring上模拟@Validated带注释的控制器方法的ConstraintValidator

时间:2016-07-22 07:16:01

标签: spring spring-mvc spring-boot mockmvc spring-validator

使用Spring Boot 1.3.6.RELEASE,我试图使用Junit,Mockito和MockMvc对控制器方法进行单元测试。我已经构建了一个自动装配服务的自定义约束验证器(扩展ConstraintValidator)。我的目标实体使用自定义验证器注释和组进行注释。我的控制器方法签名如下:

@RequestMapping ( value = "api/task/newtask", method = RequestMethod.POST )
public ResponseEntity submitTask ( @Validated ( value = TaskDependenciesDbValidation.class ) 
                                   @RequestBody Task task )
我的自定义验证器

的isValid(..)方法

@Override
    public boolean isValid ( 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;
        }
    }

在运行时,一切正常。

我想通过完全模拟我的自定义约束验证器来单元测试我的控制器的submitTask方法,或者只是模拟验证器使用的 ticketService

我尝试使用Mockito.when(..)“传统地”(不是同时)模拟服务和验证器但是我在服务上得到了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() );
    }
}

3 个答案:

答案 0 :(得分:1)

您可以使用JMockit's @Mocked annotation完全模拟验证程序:

$tag = implode(', ', array_map(function ($tag) {
          return $tag['tag_name'];
        }, $array));

至于你的例子,如果你是单元测试控制器,为什么要调出整个Spring上下文?因为这就是你正在做的事情

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

首先,你应该删除它们并再试一次。

答案 1 :(得分:0)

这是一个老问题,但我想我会提供一个对我有用并且非常直接的答案。不确定第一次提出问题时是否可用。

@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 上执行所有验证,您可以搜索您正在测试的违规行为。

答案 2 :(得分:-1)

我通过tomasz_kusmierczyk跟随this post了解它

可以找到我创建的约束验证工厂here,并且可以找到“测试验证器”的创建here

我的自定义验证程序(TicketExistsValidatorUsersExistValidator)在内部使用服务(分别为TicketServiceUserService)。我在测试中创建了这些服务的模拟,然后将模拟的服务传递给工厂(然后使用验证器的setters来设置模拟服务。)

之后,可以使用mockito.when()来模拟服务。

为了使注释约束(@NotNull,@ Size)能够在测试中工作,我必须使用@Valid和@Validated(CustomGroup.java)来注释我的控制器。