Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
762 views
in Technique[技术] by (71.8m points)

validation - Spring MockMVC - How to mock custom validators running outside of controllers

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...