我为我正在使用的界面创建了一个自定义的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
似乎无法保证首先执行哪个方法。
答案 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