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