在自定义Hamcrest匹配器中缓存变量

时间:2017-05-30 10:11:26

标签: java hamcrest

我为我正在使用的界面创建了一个自定义的Hamcrest匹配器。

匹配器是TypeSafeMatcher的一个实例,它会覆盖以下三种方法:

  • TypeSafeMatcher#matchesSafely(T item)boolean
  • TypeSafeMatcher#describeMismatchSafely(T item, Description mismatchDescription)void
  • TypeSafeMatcher#describeTo(Description description)void

我匹配的类处理某种类型对象的验证。它来自外部库,所以我不能简单地改变它。我们称这个班为ValidationSubject

此类的每个ValidationSubject实例都定义了要执行的验证背后的一些逻辑。这是通过实现ValidationSubject#validate(ValidationData validationData)完成的,其中validationData是一个构建器类型的对象,允许程序员根据实现ValidationSubject的类的对象的状态报告验证错误

public class Foo implements ValidationSubject {

    private String state;

    private Map<String, Baz> moreState;

    // constructor, methods affecting the state

    // this method is required by ValidationSubject
    @Override
    public void validate(ValidationData validationData) {
        /*
         * call methods on validationData based on the state
         * of the object
         */
    }
}

我正在使用我的匹配器来测试每个具体类中实现的验证逻辑,例如Foo

为了做到这一点,我需要在每个测试用例中存根/模拟/侦听ValidationData的实例,并根据执行的逻辑看看ValidationData对象的状态是如何变化的受试者。这是很多样板。我希望我的匹配器将其抽象出去

assertThat(testedValidationSubject, hasValidationErrors("Illegal character in name", "Description exceeds 200 words", "Age cannot be negative"));

在这种情况下,我真正匹配hasValidationErrors匹配器的参数是一组字符串值,被测对象存储在ValidationData对象中。

提取这些值需要一些代码。

return new TypeSafeMatcher<ValidationSubject>() {

    @Override
    protected boolean matchesSafely(ValidationSubject item) {
        // this calls the relevant methods on 'item' internally
        Validator validator = new Validator(item);
        List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
                .getMessages();
        Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
                .collect(Collectors.toSet());
        Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
        Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
        Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);
        return SetUtils.union(unexpectedMessages, missingMessages).isEmpty();
    }

    @Override
    public void describeMismatchSafely(final ValidationSubject item, final Description description) {
                        // this calls the relevant methods on 'item' internally
        Validator validator = new Validator(item);
        List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
                .getMessages();
        Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
                .collect(Collectors.toSet());
        Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
        Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
        Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);
        description.appendText("Validation errors were missing or unexpected\n")
                .appendValueList("\tSupefluous messages: ", ", ", "\n", unexpectedMessages.toArray())
                .appendValueList("\tMissing messages: ", ", ", "\n", missingMessages.toArray());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("validation should result in the expected errors");
    }
}

这段代码是逐行重复的:

Validator validator = new Validator(item);
List<ValidationMessage> errorMessages = validator.getErrorMessageGroup()
        .getMessages();
Set<String> actualMessages = errorMessages.stream().map(e -> e.getMessage())
        .collect(Collectors.toSet());
Set<String> expectedMessages = Stream.of(expectedErrors).collect(Collectors.toSet());
Set<String> missingMessages = SetUtils.difference(expectedMessages, actualMessages);
Set<String> unexpectedMessages = SetUtils.difference(actualMessages, expectedMessages);

我可以通过将这个片段包装在方法或lambda表达式中来回避重复(返回一对集合或接受一个函数来计算我需要的布尔值或字符串)但理想情况下,我我希望只执行一次

我需要item来计算matchesSafely的结果和describemisMatchSafely输出的消息,但每次都作为参数传递。它不是静态方法hasValidationErrors的参数,所以我看不到将结果缓存在几个变量中的简洁方法。

我可能会在其中一个方法中执行此代码并将其缓存在一个字段中,但the Javadoc for TypeSafeMatcher似乎无法保证首先执行哪个方法。

1 个答案:

答案 0 :(得分:0)

如果我了解您要做的事情,那么您正在寻找TypeSafeDiagnosingMatcher提供的功能。尝试扩展而不是 [ValidateInput(false)] [HttpPost] public ActionResult RegisterFiles(FormCollection form) { if (Request.Files.Count > 0) { try { string message = ""; // Get all files from Request object HttpFileCollectionBase files = Request.Files; for (int i = 0; i < files.Count; i++) { //string path = AppDomain.CurrentDomain.BaseDirectory + "Uploads/"; //string filename = Path.GetFileName(Request.Files[i].FileName); HttpPostedFileBase file = files[i]; string fname; // Checking for Internet Explorer if (Request.Browser.Browser.ToUpper() == "IE" || Request.Browser.Browser.ToUpper() == "INTERNETEXPLORER") { string[] testfiles = file.FileName.Split(new char[] { '\\' }); fname = testfiles[testfiles.Length - 1]; } else { fname = file.FileName; } message += "filename = " + fname + " "; // Get the complete folder path and store the file inside it. //fname = Path.Combine(Server.MapPath("~/Uploads/"), fname); //file.SaveAs(fname); } return Json(new { success = true, message = message, }); } catch (Exception ex) { //registration complete. return Json(new { success = false, message = "An error occured uploading your files. " + ex.Message, }); } } else { //registration complete. return Json(new { success = true, message = "redirect", }); } }

TypeSafeMatcher