自定义Hamcrest Matcher中的缓存变量



我为我使用的接口创建了一个自定义的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
         */
    }
}

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

为了做到这一点,我需要在每个测试用例中均需将ValidationData的实例存根,并查看ValidationData对象的状态如何根据受测试对象执行的逻辑进行更改。这是很多样板。我希望我的匹配项抽象

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

在这种情况下,我真正与hasValidationErrors Matcher的参数相匹配的是一组字符串值,该值存储在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 unexpectedn")
                .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表达式中(返回一组或接受作为参数a函数来计算我需要的布尔或字符串(来摆脱重复,但理想情况下,我'd喜欢仅执行一次

我需要item来找出matchesSafely的结果和describemisMatchSafely输出的消息的结果,但是每次将其作为参数传递时。这不是静态方法hasValidationErrors的参数

我可以在其中一种方法中执行此代码并在字段中缓存,但是TypeSafeMatcher的Javadoc似乎不保证首先执行哪种方法。

如果我了解您要做的事情,您正在寻找TypeSafeDiagnosingMatcher提供的功能。尝试将其扩展而不是TypeSafeMatcher

return new TypeSafeDiagnosingMatcher<ValidationSubject>() {
    @Override
    protected boolean matchesSafely(ValidationSubject item, Description mismatchDescription) {
        // 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);
        mismatchDescription.appendText("Validation errors were missing or unexpectedn")
                .appendValueList("tSuperfluous messages: ", ", ", "n", unexpectedMessages.toArray())
                .appendValueList("tMissing messages: ", ", ", "n", missingMessages.toArray());
        return SetUtils.union(unexpectedMessages, missingMessages).isEmpty();
    }
    @Override
    public void describeTo(Description description) {
        description.appendText("validation should result in the expected errors");
    }
}

最新更新