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

13,058

Solution 1

I got it working by following this post by tomasz_kusmierczyk

The constraint validation factory i created can be found here and the creation of the "test validator" can be found here.

My custom validators (TicketExistsValidator and UsersExistValidator) make use of services internally (TicketService and UserService respectively). I create mocks of these services on my test and then pass in the mocked services to the factory (which then uses the setters of the validators to set the mock services).

After that, services can be mocked by using mockito.when().

In order for the annotation constraints (@NotNull, @Size) to work in tests, i had to annotate my controller with both @Valid and @Validated(CustomGroup.java).

Solution 2

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.

Share:
13,058

Related videos on Youtube

alextsil
Author by

alextsil

Mainly interested in web services, large system architecture, microservices etc.

Updated on June 04, 2022

Comments

  • alextsil
    alextsil almost 2 years

    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() );
        }
    }
    
    • jbarat
      jbarat almost 8 years
      Can you add the test you wrote?
    • Miloš Milivojević
      Miloš Milivojević almost 8 years
      Yeah, adding your test code would be most helpful, without it we're only guessing at what went wrong.
    • alextsil
      alextsil almost 8 years
      Added one test attempt. The other attempt i made was to annotate the validator with @Mock on the test class declarations and then in the test method : when(myValidator.isValid(any..,any..)).thenReturn(true);
    • alextsil
      alextsil almost 8 years
    • lrkwz
      lrkwz about 3 years
  • vvursT
    vvursT almost 7 years
    nice solution. works for me and is imho the better solution than configuring a individual TestingContraintValidatorFactory which supports DI of specific mocks.
  • Alex
    Alex almost 3 years
    Dear @Alextsil the code seems very neat but after setting all, still my validator is ignored when testing. I have everything like you. I had a custom validator and its interface in place and I have added the validation factory as you did (Adding my own service in it as my validator calls an intra service). Then I'm getting the LocalValidatorFactoryBean from the factory and adding it to the mockMvc with .setValidator, but still it doesn't get into my custom validator and starts from the controller method. Can you give me some hints on what could be happening or how to debug it?