在编写单元测试事后代码以编写另一个项目创建的代码时,我遇到了这个问题,即如何模拟通过initBinder
绑定(bind)到 Controller 的 validator ?
通常,我只是考虑确保输入有效,并在 validator 中进行一些额外的调用,但是在这种情况下, validator 类与通过一些数据源进行检查相结合,因此测试变得一团糟。耦合可以追溯到使用的一些旧的通用库,并且超出了我当前修复所有库的范围。
最初,我尝试使用PowerMock和静态方法模拟出 validator 的外部依赖关系,但最终遇到了一个类,该类在创建类时需要数据源,却找不到解决该问题的方法。
然后,我尝试仅使用普通的模仿工具来模拟 validator ,但这也不起作用。然后尝试在mockMvc
调用中设置 validator ,但这并没有为 validator 注册@Mock
注释。终于遇到了this question。但是,由于 Controller 本身没有字段validator
,因此这也将失败。那么,我该如何解决这个问题呢?
验证者:
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);
[...]
}
[...]
}
Controller :
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));
}
[...]
}
异常(exception):
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应用程序中使用new
创建业务对象。您应该始终从应用程序上下文中获取它们-这将简化在测试中对它们的模拟。
在您的用例中,您应该简单地将 validator 创建为bean(例如defaultTerminationValidator
),然后将其注入(inject)到 Controller 中:
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[**]", "terminationDate",
"accountSelection", "iban", "bic" });
}
[...]
}
这样,您就可以在测试中简单地注入(inject)一个模拟了。