alextsil
alextsil

Reputation: 554

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

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

Upvotes: 4

Views: 20490

Answers (3)

Kevin M
Kevin M

Reputation: 2903

This is an old question, but thought I'd provide an answer that worked for me and is pretty straight forward. Not sure if it was available when the question was first asked or not.

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

It's the ValidationAutoConfiguration.class as the configuration of the test that does the heavy lifting. This will exercise all validation on the ObjectToBeValidated and you can search the violations for just that one you're testing for.

Upvotes: 1

alextsil
alextsil

Reputation: 554

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).

Upvotes: 2

Miloš Milivojević
Miloš Milivojević

Reputation: 5369

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.

Upvotes: 2

Related Questions