我在测试中有一段代码,使用Hamcrest 2.2检查结果列表是否包含某些属性:
assertThat(result.getUsers(), hasItem(
hasProperty("name", equalTo(user1.getName()))
));
assertThat(result.getUsers(), hasItem(
hasProperty("name", equalTo(user2.getName()))
));
当NameDto
是一个普通的类时,这个工作非常好。但是在我将它更改为记录
之后,Hamcrest的hasProperty
抱怨没有名为name
的属性:
java.lang.AssertionError:
Expected: a collection containing hasProperty("name", "Test Name")
but: mismatches were: [No property "name", No property "name"]
是否有其他匹配器可以用来实现与之前相同的匹配?或者其他解决方法可以用来让它与记录一起工作?
Hamcrest实际上遵循JavaBeans标准(该标准允许任意访问器名称),因此我们可以使用hasProperty
实现这一点。如果你想的话。不过,我不确定你会不会这样做——这很麻烦。
如果我们遵循HasPropertyWithValue
的源代码的工作方式,我们会发现它通过在相关类的BeanInfo
中找到属性的PropertyDescriptor
来发现访问器方法的名称,该属性是通过java检索的。豆。内省者
。
对于给定类的BeanInfo是如何解析的,内探器
有一些非常有用的留档:
Introspector类为工具提供了一种标准方法,用于了解目标JavaBean支持的属性、事件和方法。
对于这三种信息中的每一种,内省者将分别分析bean的类和超类,寻找显式或隐式信息,并使用这些信息构建全面描述目标bean的BeanInfo对象。
对于每个类“Foo”,如果存在相应的“FooBeanInfo”类,在查询信息时提供非空值,则可以使用显式信息。我们首先通过获取目标bean类的完整包限定名并附加“BeanInfo”来寻找BeanInfo类,以形成一个新的类名。如果失败,那么我们将使用该名称的最后一个classname组件,并在BeanInfo包搜索路径中指定的每个包中查找该类。
因此,对于“sun.xyz.OurButton”这样的类,我们首先会查找名为“sun.xyz.ourButtonBeaInfo”的BeanInfo类,如果失败,我们会在BeanInfo搜索路径中的每个包中查找OurButtonBeaInfo类。在默认搜索路径下,这意味着要查找“sun.beans.infos.OurButtonBeanInfo”。
如果一个类提供了关于自身的显式BeanInfo,那么我们将其添加到我们从分析任何派生类中获得的BeanInfo信息中,但我们认为显式信息对于当前类及其基类是确定的,并且不在超类链的任何更上一层。
如果我们在类上找不到显式BeanInfo,我们使用低级反射来研究类的方法,并应用标准设计模式来识别属性访问器、事件源或公共方法。然后,我们继续分析类的超类,并添加来自它的信息(可能在超类链上)。
你可能认为内探器
可以在最后一步(我们使用低级反射)中查找记录并生成正确的BeanInfo
,但似乎不是。如果你谷歌一下,你会在JDK开发列表中找到一些关于添加这个的讨论,但似乎什么也没发生。可能是JavaBeans规范需要更新,我想这可能需要一些时间。
但是,为了回答您的问题,我们所要做的就是为您拥有的每种记录类型提供一个BeanInfo
。然而,手写它们并不是我们想要做的事情——它甚至比用getter和setter(以及equals
和hashCode
等等)编写类的老式方式还要糟糕。
我们可以作为构建步骤自动生成bean信息(或者在启动应用程序时动态生成)。一种比较简单的方法(需要一些样板文件)是制作一个通用的BeanInfo
,它可以用于所有记录类。这里有一个最省力的方法。首先,假设我们有这样的记录:
public record Point(int x, int y){}
还有一个将其视为bean的主类:
public class Main {
public static void main(String[] args) throws Exception {
var bi = java.beans.Introspector.getBeanInfo(Point.class);
var bean = new Point(4, 2);
for (var prop : args) {
Object value = Stream.of(bi.getPropertyDescriptors())
.filter(pd -> pd.getName().equals(prop))
.findAny()
.map(pd -> {
try {
return pd.getReadMethod().invoke(bean);
} catch (ReflectiveOperationException e) {
return "Error: " + e;
}
})
.orElse("(No property with that name)");
System.out.printf("Prop %s: %s%n", prop, value);
}
}
}
如果我们像javamainxyz
那样编译和运行,您会得到如下输出:
Prop x: (No property with that name)
Prop y: (No property with that name)
Prop z: (No property with that name)
所以它没有像预期的那样找到记录组件。让我们制作一个通用的BeanInfo
:
public abstract class RecordBeanInfo extends java.beans.SimpleBeanInfo {
private final PropertyDescriptor[] propertyDescriptors;
public RecordBeanInfo(Class<?> recordClass) throws IntrospectionException {
if (!recordClass.isRecord())
throw new IllegalArgumentException("Not a record: " + recordClass);
var components = recordClass.getRecordComponents();
propertyDescriptors = new PropertyDescriptor[components.length];
for (var i = 0; i < components.length; i++) {
var c = components[i];
propertyDescriptors[i] = new PropertyDescriptor(c.getName(), c.getAccessor(), null);
}
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.clone();
}
}
有了这个类在我们的工具箱中,我们所要做的就是用一个正确名称的类来扩展它。在我们的示例中,PointBeanInfo
与Point
记录在同一个包中:
public class PointBeanInfo extends RecordBeanInfo {
public PointBeanInfo() throws IntrospectionException {
super(Point.class);
}
}
所有这些都准备就绪后,我们运行我们的主类并获得预期的输出:
$ java Main x y z
Prop x: 4
Prop y: 2
Prop z: (No property with that name)
结束语:如果你只是想使用属性来使单元测试看起来更好,我建议使用其他答案中给出的解决方法之一,而不是我提出的过度设计的方法。
我发现,仅使用AssertJ就可以实现相同的测试,至少在这种情况下:
assertThat(result.getUsers())
.extracting(UserDto::name)
.contains(user1.getName(), user2.getName());
它没有使用hasProperty
,所以它并不能完全解决这个问题。
记录字段的访问器方法不遵循常规的JavaBeans约定,因此用户
记录(比如public record User(String name){}
)将有一个名为name()
的访问器方法,而不是getName()
。
我怀疑这就是为什么汉克雷斯特认为这里没有房产。我认为在Hamcrest,除了写一个定制的匹配器之外,没有其他方法可以跳出框框。
以下是一个定制的HasRecordComponentWithValue
,灵感来自现有的hasprropertywhithvalue
。这里使用的主要工具是Java的类。getRecordComponents()
:
public static class HasRecordComponentWithValue<T> extends TypeSafeDiagnosingMatcher<T> {
private static final Condition.Step<RecordComponent,Method> WITH_READ_METHOD = withReadMethod();
private final String componentName;
private final Matcher<Object> valueMatcher;
public HasRecordComponentWithValue(String componentName, Matcher<?> valueMatcher) {
this.componentName = componentName;
this.valueMatcher = nastyGenericsWorkaround(valueMatcher);
}
@Override
public boolean matchesSafely(T bean, Description mismatch) {
return recordComponentOn(bean, mismatch)
.and(WITH_READ_METHOD)
.and(withPropertyValue(bean))
.matching(valueMatcher, "record component'" + componentName + "' ");
}
private Condition.Step<Method, Object> withPropertyValue(final T bean) {
return new Condition.Step<Method, Object>() {
@Override
public Condition<Object> apply(Method readMethod, Description mismatch) {
try {
return matched(readMethod.invoke(bean, NO_ARGUMENTS), mismatch);
} catch (Exception e) {
mismatch.appendText(e.getMessage());
return notMatched();
}
}
};
}
@Override
public void describeTo(Description description) {
description.appendText("hasRecordComponent(").appendValue(componentName).appendText(", ")
.appendDescriptionOf(valueMatcher).appendText(")");
}
private Condition<RecordComponent> recordComponentOn(T bean, Description mismatch) {
RecordComponent[] recordComponents = bean.getClass().getRecordComponents();
for(RecordComponent comp : recordComponents) {
if(comp.getName().equals(componentName)) {
return matched(comp, mismatch);
}
}
mismatch.appendText("No record component \"" + componentName + "\"");
return notMatched();
}
@SuppressWarnings("unchecked")
private static Matcher<Object> nastyGenericsWorkaround(Matcher<?> valueMatcher) {
return (Matcher<Object>) valueMatcher;
}
private static Condition.Step<RecordComponent,Method> withReadMethod() {
return new Condition.Step<RecordComponent, java.lang.reflect.Method>() {
@Override
public Condition<Method> apply(RecordComponent property, Description mismatch) {
final Method readMethod = property.getAccessor();
if (null == readMethod) {
mismatch.appendText("record component \"" + property.getName() + "\" is not readable");
return notMatched();
}
return matched(readMethod, mismatch);
}
};
}
@Factory
public static <T> Matcher<T> hasRecordComponent(String componentName, Matcher<?> valueMatcher) {
return new HasRecordComponentWithValue<T>(componentName, valueMatcher);
}
}
我试着 我在调试时获得和是。 如何在使用时断言大于条件
问题内容: 我有一些正在测试的代码,它们呼吁Java记录器报告其状态。在JUnit测试代码中,我想验证是否在此记录器中输入了正确的日志条目。遵循以下内容: 我想可以用专门改编的记录器(或处理程序或格式化程序)来完成此操作,但我希望重用已存在的解决方案。(而且,老实说,我不清楚如何从记录器中获取logRecord,但是假设这是可能的。) 问题答案: 我也需要几次。我在下面整理了一个小样本,你可以根据
我正在使用JUnit和Apache Log4J来学习TDD和日志服务的最佳实践。我有一个类,它有一个方法,它将尝试连接到MySQL数据库并返回类型的对象。 我有一个类GenericTaskInterpreterTests,在这里我为这个方法(和其他方法)编写了测试用例。 在这个测试用例场景中,如何使用TestWatcher记录断言失败
我正在寻找一些方法(最好是通过Java代码),使我能够通过SSO登录从IDP(即ForgeRock的OpenAM)获取服务提供商(SP)的SAML断言。 SP已配置为与IDP交互(例如vCloud Director- 我似乎找不到任何执行SSO登录的方法(通过使用IDP的SAML断言进行身份验证)。到目前为止,我已经看到了十几条参考文献,包括断言(AssertionIDRequestUtil)。来
问题内容: 我想知道为什么关键字在Java中使用不足?我几乎从未见过使用它们,但是我认为它们是个好主意。我当然更喜欢简洁: 冗长的 我怀疑它们没有得到充分利用,因为 他们来得相对较晚(Java 1.4),到那时,许多人已经建立了Java编程风格/习惯 它们默认在运行时关闭 问题答案: __理论上, 断言 是用于测试不变式的假设,这些假设 必须 正确才能使代码正确完成。 所示示例测试了有效输入,这不
我正在尝试使用谷歌办公套件设置SAML。但是我得到了这个错误 我已经配置断言应该在我的SP中签名。我可以在其他IDP(如onelogin、okta)中看到签署响应断言的选项。该配置适用于其他IDP,但看不到在谷歌办公套件SAML中唱响应断言的选项。 在Gsuite中,我只能看到签名响应的选项,而不能看到断言。 如何正确设置此设置? SP元数据如下所示: