为了使用Bean验证报告Spring MVC中的全局错误,我们可以创建一个自定义的类级别约束注释。 全局错误与已验证Bean中的任何特定字段都不相关。 在本文中,我将展示如何使用Spring Test编写测试,以验证给定的model属性是否存在全局验证错误。
自定义(类级别)约束
为了本文的方便,我创建了一个相对简单的类级别约束,称为SamePassword
,并由SamePasswordValidator
进行了SamePasswordValidator
:
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = SamePasswordsValidator.class)
@Documented
public @interface SamePasswords {String message() default "passwords do not match";Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};
}
如下所示,验证器非常简单:
public class SamePasswordsValidator implements ConstraintValidator<SamePasswords, PasswordForm> {@Overridepublic void initialize(SamePasswords constraintAnnotation) {}@Overridepublic boolean isValid(PasswordForm value, ConstraintValidatorContext context) {if(value.getConfirmedPassword() == null) {return true;}return value.getConfirmedPassword().equals(value.getPassword());}
}
PasswordForm
只是一个带有一些约束注释的POJO,包括我刚创建的POJO:
@SamePasswords
public class PasswordForm {@NotBlankprivate String password;@NotBlankprivate String confirmedPassword;// getters and setters omitted for redability}
@Controller
控制器有两种方法:显示表单和处理表单的提交:
@Controller
@RequestMapping("globalerrors")
public class PasswordController {@RequestMapping(value = "password")public String password(Model model) {model.addAttribute(new PasswordForm());return "globalerrors/password";}@RequestMapping(value = "password", method = RequestMethod.POST)public String stepTwo(@Valid PasswordForm passwordForm, Errors errors) {if (errors.hasErrors()) {return "globalerrors/password";}return "redirect:password";}
}
当密码验证失败时,将在BindingResult
(在上面的示例中为Errors
中注册一个全局错误。 例如,我们可以在HTML页面的表单顶部显示此错误。 在Thymeleaf中,这将是:
<div th:if="${#fields.hasGlobalErrors()}"><p th:each="err : ${#fields.globalErrors()}" th:text="${err}">...</p>
</div>
集成测试与Spring Test
让我们设置一个集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class AccountValidationIntegrationTest {@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@Beforepublic void setUp() throws Exception {mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();}
}
第一个测试验证发送带有空的表单password
和confirmedPassword
失败:
@Testpublic void failsWhenEmptyPasswordsGiven() throws Exception {this.mockMvc.perform(post("/globalerrors/password").param("password", "").param("confirmedPassword", "")).andExpect(model().attributeHasFieldErrors("passwordForm", "password", "confirmedPassword")).andExpect(status().isOk()).andExpect(view().name("globalerrors/password"));}
在上面的示例中,测试将验证password
字段和confirmedPassword
字段是否均存在字段错误。
同样,我想验证当给定的密码不匹配时,是否出现特定的全局错误。 因此,我期望这样的事情: .andExpect(model().hasGlobalError("passwordForm", "passwords do not match"))
。 不幸的是,由MockMvcResultMatchers#model()
返回的ModelResultMatchers
没有提供断言给定模型属性具有全局错误的方法。
由于不存在该匹配器,因此我创建了从ModelResultMatchers
扩展的自己的匹配器。 Java 8版本的代码如下:
public class GlobalErrorsMatchers extends ModelResultMatchers {private GlobalErrorsMatchers() {}public static GlobalErrorsMatchers globalErrors() {return new GlobalErrorsMatchers();}public ResultMatcher hasGlobalError(String attribute, String expectedMessage) {return result -> {BindingResult bindingResult = getBindingResult(result.getModelAndView(), attribute);bindingResult.getGlobalErrors().stream().filter(oe -> attribute.equals(oe.getObjectName())).forEach(oe -> assertEquals("Expected default message", expectedMessage, oe.getDefaultMessage()));};}private BindingResult getBindingResult(ModelAndView mav, String name) {BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name);assertTrue("No BindingResult for attribute: " + name, result != null);assertTrue("No global errors for attribute: " + name, result.getGlobalErrorCount() > 0);return result;}
}
通过上述添加,我现在可以验证全局验证错误,如下所示:
import static pl.codeleak.demo.globalerrors.GlobalErrorsMatchers.globalErrors;@Test
public void failsWithGlobalErrorWhenDifferentPasswordsGiven() throws Exception {this.mockMvc.perform(post("/globalerrors/password").param("password", "test").param("confirmedPassword", "other")).andExpect(globalErrors().hasGlobalError("passwordForm", "passwords do not match")).andExpect(status().isOk()).andExpect(view().name("globalerrors/password"));
}
如您所见,扩展Spring Test的匹配器并提供给您自己的匹配器相对容易,可用于改进集成测试中的验证验证。
资源资源
- 可以在这里找到本文的源代码: https : //github.com/kolorobot/spring-mvc-beanvalidation11-demo 。
翻译自: https://www.javacodegeeks.com/2014/08/spring-mvc-integration-testing-assert-the-given-model-attributes-have-global-errors.html