How to mock custom validators when unit testing

In the previous post we saw how dependencies in custom constraint validators can be mocked when we want to unit test our validation layer in a Spring Boot application. It all boils down to the creation of a custom ValidatorFactory that can use our custom ConstraintValidators, those will have their dependencies mocked and manually injected.

But what if we want to mock an entire ConstraintValidator rather than just its dependencies? There are some situations where we may want to do this: for example, the logic in the custom constraint validator might be very complex. In this case we could choose to mock the constraint validator in the test suite for the entire validation layer, and create a separate test suite only for that custom constraint validator class, where its dependencies will be mocked.

The problem: mocked ConstraintValidator not working with the custom ValidatorFactory

Unfortunately, if we try to mock a ConstraintValidator and inject it in our CustomLocalValidatorFactoryBean, when we run our test cases we’ll get an unexpected exception. To prove this we’re creating a test suite which is identical to test suite from the previous post, the only difference is that we’re not mocking anymore the dependency in our UniqueSsnValidator, we’re mocking the UniqueSsnValidator altogether. by default the isValid(Employee, ConstraintValidatorContext) method will return true, while there’ll be a test case where the method will be mocked to return false, thus triggering a ConstraintViolationException.

The stacktrace we get after running the tests is the same we got when we tried to perform validation using the ValidatorImpl returned by the DefaultValidatorFactory: this time our mocked UniqueSsnValidator wasn’t picked up by the decorated ConstraintValidatorFactory, but why?

org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <javax.validation.ConstraintViolationException> but was: <javax.validation.ValidationException>
…
Caused by: javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: com.codemadeclear.mockvalidatordependencies.validation.validators.UniqueSsnValidator.
	at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:44)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.run(ConstraintValidatorFactoryImpl.java:43)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.getInstance(ConstraintValidatorFactoryImpl.java:28)
	at com.codemadeclear.mockvalidatordependencies.CustomLocalValidatorFactoryBean$1.getInstance(CustomLocalValidatorFactoryBean.java:34)
	at org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor.newInstance(ClassBasedValidatorDescriptor.java:84)

The problem lies in the line number 31 of the CustomLocalValidatorFactoryBean. The method getClass() called on the mocked instance of a constraint validator does not return UniqueSsnValidator, because it’s a proxy created by Mockito using CGLIB. We can easily verify this by placing a breakpoint and running our test in debug mode.

As we can see, the mocked object’s class is not UniqueSsnValidator, therefore the equals(Object) method returns false and our custom validator factory fails in doing its job. But at the same time we can see that the proxy is aware of the class being mocked (compare the attribute typeToMock with the variable key), and we can make good use of this.

The solution: let the custom ValidatorFactory recognize mocks

This problem can be easily fixed with a little update to our CustomLocalValidatorFactoryBean. Notice the added private method isMockOf(ConstraintValidator<?, ?>, Class<T>).

Mockito provides the utility method mockingDetails(Object), which allows to extract all the relevant information from a proxy created by Mockito where applicable. in our case we’re interested in retrieving the typeToMock property and see if it matches the required ConstraintValidator type. Now even the mocked ConstraintValidators can be used in our test suites. If we run the test suite again all tests will be green.

Conclusion

Mocking dependencies in custom constraint validators is great to unit test the validation layer without the need to bootstrap the entire Spring context: the test execution is fast and the relevant classes can be tested in isolation, which makes our tests reliable. Sometimes we might want to go a bit further and mock an entire constraint validator. We’ve shown how this is also possible, all it takes is a little tweaking of our testing tools.

The example project can be downloaded from GitLab.