3
@UsernameAlreadyExists
private String username;

I have a custom validator that I created to ensure that duplicate usernames are caught by the application when account creation form submits.

When I unit test the account creation controller using MockMVC, it fails as the validator depends on a service, so I get null pointer exception.

How can I mock the validator or the service this validator depends on? I could not figure out how to make this work as the controller does not depend on the validator explicitly, it runs outside of the controller.

led
  • 611
  • 4
  • 11
  • 18

2 Answers2

10

When testing with the use of 'standaloneSetup' that is without loading entire Spring context, validation is triggered internally by the framework call on SpringWebConstraintValidatorFactory. In order to connect to that flow you need to set new instance of SpringWebConstraintValidatorFactory in 'mockMvc'.
Unfortunatelly there isn't an easy clean way of doing so. You have to subclass SpringWebConstraintValidatorFactory and set your instance in LocalValidatorFactoryBean which in turn can be set in mockMvc. But LocalValidatorFactoryBean will need ApplicationContext. Here is an example:



    public class TestConstrainValidationFactory extends SpringWebConstraintValidatorFactory {

        private final WebApplicationContext ctx;

        private boolean isValid = false;

        public TestConstrainValidationFactory(WebApplicationContext ctx) {
            this.ctx = ctx;
        }

        @Override
        public < T extends ConstraintValidator<?, ?>> T getInstance(Class key) {
            ConstraintValidator instance = super.getInstance(key);
            if (instance instanceof DeviceValidator) {
                DeviceValidator deviceValidator = (DeviceValidator) instance;
                deviceValidator.setYourAutowiredField((String id, String type) -> isValid); //change that to suit your needs
                instance = deviceValidator;
            }
            return (T) instance;
        }

        @Override
        protected WebApplicationContext getWebApplicationContext() {
            return ctx;
        }

    }

Example of connecting that to mockMvc



    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = MockServletContext.class)
    @WebAppConfiguration()
    public class DevicesControllerTest {
        @Autowired
        private MockServletContext servletContext;
        @InjectMocks
        private DevicesController devicesController;
        private TestConstrainValidationFactory constraintFactory;

        @Before
        public void setUp() {
            MockitoAnnotations.initMocks(this);

            final GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext);
            final ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
            beanFactory.registerSingleton(DeviceValidator.class.getCanonicalName(), new DeviceValidator());
            context.refresh();

            LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
            validatorFactoryBean.setApplicationContext(context);
            constraintFactory = new TestConstrainValidationFactory(context);
            validatorFactoryBean.setConstraintValidatorFactory(constraintFactory);
            validatorFactoryBean.setProviderClass(HibernateValidator.class);
            validatorFactoryBean.afterPropertiesSet();

            this.mockMvc = MockMvcBuilders
                    .standaloneSetup(devicesController)
                    .setValidator(validatorFactoryBean)
                    .setHandlerExceptionResolvers()
                    .build();
        }
    }

Once you have it, ReflectionTestUtils.setField(constraintFactory, "isValid", false); will work as expected and you can set fields in the validator via the factory.
View issue context spring-projects/spring-test-mvc/issues:

  • How can i use this for more that 1 validator at the same time? – alextsil Jul 25 '16 at 14:44
  • 1
    I would try by adding another 'instanceof' check in getInstance method in TestConstrainValidationFactory (to have another validator setup and returned) and then registering in setUp() test method analogically. – tomasz_kusmierczyk Jul 26 '16 at 12:15
  • (String id, String type) -> isValid ... what is mean of it . .can you please explain? – praveen jain Mar 17 '18 at 09:27
  • @praveenjain no need to brood over this, what matters is that the access to a custom validator is there and you can set fields in it. It all depends on what your custom validator does. I only gave it as an example. – tomasz_kusmierczyk Mar 22 '18 at 14:48
  • @tomas_kusmierczyk I finding this very useful but Im still stuck in a little detail. You are setting as provider class a HibernateValidator, but I'm not using a hibernate validator whatsoever. I have m own custom validator which would be your DeviceValidator (which implements ConstraintValidator with its very own interface). Shouldn't I use a spring general validator instead of that hibernate validator? I need some hints on that because I'm stuck trying to bind all the pieces. – Alex Apr 29 '21 at 15:14
2

You cannot mock the ConstraintValidator but you certainly can mock the service that the validator depends on, using the usual spring ways of mocking beans, for eg.:

.1. Potentially define a mock instance with the exact same bean name, ensuring that your config with mock gets loaded after the real instance.

.2. Test with only the test configuration with only the mock bean defined.

Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • Can you give an example? I am using standalone configuration by the way. The validator is created and controlled by Spring. Even if I create a mock @Mock CustomService customService; how I am going to inject it to the validator? – led Apr 18 '14 at 20:36
  • If you can share your sample through some means say gist or github project, I can enhance it and show you. – Biju Kunjummen Apr 19 '14 at 11:08
  • I used WebApplicationContext instead of standalone configuration and now I do not have any issues. – led Apr 22 '14 at 23:29