Mock a ConstraintValidator of a @Validated annotated controller method on Spring

alextsil picture alextsil · Jul 22, 2016 · Viewed 10.4k times · Source

Using Spring Boot 1.3.6.RELEASE, i am trying to unit test a controller method using Junit, Mockito and MockMvc. I have built a custom constraint validator (extending ConstraintValidator) which autowires a service. My target entity is annotated with the custom validator annotation and a group. My controller method signature is the following :

@RequestMapping ( value = "api/task/newtask", method = RequestMethod.POST )
public ResponseEntity submitTask ( @Validated ( value = TaskDependenciesDbValidation.class ) 
                                   @RequestBody Task task )
isValid(..) method of my custom validator

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

On runtime, everything works fine.

I would like to unit test my controller's submitTask method by mocking my custom constraint validator entirely or just mock the ticketService that the validator uses.

I have tried mocking the service and the validator "traditionally" (not at the same time) with Mockito.when(..) but i am getting a NullPointerException on the service.

Edit :

Test attempt :

@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() );
    }
}

Answer

Miloš Milivojević picture Miloš Milivojević · Jul 22, 2016

You can mock the validator entirely by using 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
    }
}

As for your example, if you're unit testing the controller, why bring up the whole Spring context? Because that's what you're doing with

@RunWith ( SpringJUnit4ClassRunner.class )
@SpringApplicationConfiguration ( classes = MyApplication.class )
@ContextConfiguration ( classes = MockServletContext.class )
@WebAppConfiguration

First and foremost, you should remove those and try again.