java – 当单元测试控制器时,模拟一个Spring Validator

在将单元测试postmortem编码到另一个项目创建的代码时,我遇到了如何使用initBinder模拟与控制器绑定的验证器的这个问题?

通常我会考虑确保我的输入是有效的,并且在验证器中进行一些额外的调用,但在这种情况下,验证器类与通过几个数据源进行检查相结合,并且测试变得相当麻烦.耦合可追溯到使用的一些旧的常用库,并且不在我目前的工作范围内来修复它们.

起初,我试图使用PowerMock和嘲笑静态方法来嘲笑验证器的外部依赖关系,但是最终遇到一个类,当类被创建时需要一个数据源,没有找到一个方法.

然后,我试图使用普通的mockito工具来嘲笑验证器,但是也没有.然后尝试将验证器设置在mockmvc调用中,但不会为验证器注册@Mock注释.最后跑到this question.但是由于控制器本身没有现场验证器,所以也失败了.那么,我该如何解决这个问题呢?

验证器:

public class TerminationValidator implements Validator {
    // JSR-303 Bean Validator utility which converts ConstraintViolations to Spring's BindingResult
    private CustomValidatorBean validator = new CustomValidatorBean();

    private Class<? extends Default> level;

    public TerminationValidator(Class<? extends Default> level) {
        this.level = level;
        validator.afterPropertiesSet();
    }

    public boolean supports(Class<?> clazz) {
        return Termination.class.equals(clazz);
    }

    @Override
    public void validate(Object model,Errors errors) {
        BindingResult result = (BindingResult) errors;

        // Check domain object against JSR-303 validation constraints
        validator.validate(result.getTarget(),result,this.level);

        [...]
    }

    [...]
}

控制器:

public class TerminationController extends AbstractController {

    @InitBinder("termination")
    public void initBinder(WebDataBinder binder,HttpServletRequest request) {
        binder.setValidator(new TerminationValidator(Default.class));
        binder.setAllowedFields(new String[] { "termId[**]","terminationDate","accountSelection","iban","bic" });
    }

    [...]
}

测试类:

@RunWith(MockitoJUnitRunner.class)
public class StandaloneTerminationTests extends BaseControllerTest {
    @Mock
    private TerminationValidator terminationValidator = new TerminationValidator(Default.class);

    @InjectMocks
    private TerminationController controller;

    private mockmvc mockmvc;

    @Override
    @Before
    public void setUp() throws Exception {
        initMocks(this);

        mockmvc = standalonesetup(controller)
                      .setCustomArgumentResolvers(new TestHandlerMethodArgumentResolver())
                      .setValidator(terminationValidator)
                      .build();

        ReflectionTestUtils.setField(controller,"validator",terminationValidator);

        when(terminationValidator.supports(any(Class.class))).thenReturn(true);
        donothing().when(terminationValidator).validate(any(),any(Errors.class));
    }

    [...]
}

例外:

java.lang.IllegalArgumentException: Could not find field [validator] of type [null] on target [my.application.web.controller.TerminationController@560508be]
    at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:111)
    at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:84)
    at my.application.web.controller.termination.StandaloneTerminationTests.setUp(StandaloneTerminationTests.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runchild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runchild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runchildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

解决方法

您应该避免在Spring应用程序中创建新的业务对象.您应该总是从应用程序的上下文中获取它们 – 它将在您的测试中轻松嘲笑它们.

在你的用例中,你应该简单地创建一个bean的验证器(比如说defaultTerminationValidator)并将它注入你的控制器中:

public class TerminationController extends AbstractController {

    private TerminationValidator terminationValidator;

    @Autowired
    public setDefaultTerminationValidator(TerminationValidator validator) {
        this.terminationValidator = validator;
    }

    @InitBinder("termination")
    public void initBinder(WebDataBinder binder,HttpServletRequest request) {
        binder.setValidator(terminationValidator);
        binder.setAllowedFields(new String[] { "termId[**]","bic" });
    }

    [...]
}

这样,您就可以在测试中简单地注入模拟.

相关文章

最近看了一下学习资料,感觉进制转换其实还是挺有意思的,尤...
/*HashSet 基本操作 * --set:元素是无序的,存入和取出顺序不...
/*list 基本操作 * * List a=new List(); * 增 * a.add(inde...
/* * 内部类 * */ 1 class OutClass{ 2 //定义外部类的成员变...
集合的操作Iterator、Collection、Set和HashSet关系Iterator...
接口中常量的修饰关键字:public,static,final(常量)函数...