由于使用 toString() 的循环引用,AssertJ 错误报告失败
AssertJ error reporting fails due to a circular reference with toString()
要测试的遗留代码具有循环引用,因此类似于多对一双向关系实体。
不幸的是,相关 类 中也有 Lombok 注释 @ToString
和 @Data
,每当调用 toString()
时就会导致堆栈溢出。
当然,我意识到这是一个有点糟糕的代码,如果确实需要 toString()
方法,应该有一些协议,并且需要的字段应该用 @ToString.Exclude
注释,以防止循环引用 toString()
。然而,目前我不能触及这段代码,但我仍然想使用 AssertJ,例如:
assertThat(list1).containsExactly(item1, item2);
而不是做一些更复杂的测试代码。
现在的问题是,测试执行没有AssertJ报告断言失败,因为当AssertJ调用[=13时这个循环引用=] 在项目上报告失败的项目是什么。
是否可能,如果可以,我如何更改 AssertJ 以在不调用 toString()
的情况下报告失败,但是 - 例如 - getId()
失败的项目?
一种解决方案是对列表使用自定义表示:
假设 class Obj
循环引用它是 toString()
。将自定义表示 class 定义为:
class CustomListRepresentation implements Representation {
public static final CustomListRepresentation instance = new CustomListRepresentation();
private CustomListRepresentation() {
}
@Override
public String toStringOf(Object) {
List<?> list = (List<?>) object;
return list.stream()
.filter(Obj.class::isInstance)
.map(Obj.class::cast).map(Obj::getId)
.map(String::valueOf)
.collect(Collectors.joining(","));
}
@Override
public String unambiguousToStringOf(Object object) {
return this.toStringOf(object);
}
}
如果除 Obj
之外的多个 classes 也有此问题,那么您可以保留这些 classes 的映射及其映射函数(用于代替 toString()
) 可以定义并适当地替换 .filter(Obj.class::isInstance).map(Obj.class::cast).map(Obj::getId)
。
现在assert语句需要修改为:
assertThat(list).withRepresentation(CustomListRepresentation.instance)
.containsExactly(item1, item2);
或者另一种方法是使用 overridingErrorMessage
方法覆盖错误消息:
private static String getError(List<Obj> list, Obj... items) {
// return a custom formatted error message.....
}
断言为:
assertThat(list).overridingErrorMessage(getError(list, item1, item2))
.containsExactly(item1, item2);
根据 我的最终结果(直到这些烦人的循环被重构掉)很简单:
Representation pres = new StandardRepresentation() {
public String toStringOf(Object object) {
return unambiguousToStringOf(object);
}
public String unambiguousToStringOf(Object object) {
return (object == null) ? "null" : object.getClass().getSimpleName() + "@" + object.hashCode();
}
};
在我的测试中就像这样(用一个愚蠢的例子class):
@ToString
public static class A {
public A a;
}
@Test
void testNoError() {
var a = new A();
a.a = a;
// Not null so AssretJ prints out failing object when test fails
assertThat(a).withRepresentation(pres).isNull();
}
要测试的遗留代码具有循环引用,因此类似于多对一双向关系实体。
不幸的是,相关 类 中也有 Lombok 注释 @ToString
和 @Data
,每当调用 toString()
时就会导致堆栈溢出。
当然,我意识到这是一个有点糟糕的代码,如果确实需要 toString()
方法,应该有一些协议,并且需要的字段应该用 @ToString.Exclude
注释,以防止循环引用 toString()
。然而,目前我不能触及这段代码,但我仍然想使用 AssertJ,例如:
assertThat(list1).containsExactly(item1, item2);
而不是做一些更复杂的测试代码。
现在的问题是,测试执行没有AssertJ报告断言失败,因为当AssertJ调用[=13时这个循环引用=] 在项目上报告失败的项目是什么。
是否可能,如果可以,我如何更改 AssertJ 以在不调用 toString()
的情况下报告失败,但是 - 例如 - getId()
失败的项目?
一种解决方案是对列表使用自定义表示:
假设 class Obj
循环引用它是 toString()
。将自定义表示 class 定义为:
class CustomListRepresentation implements Representation {
public static final CustomListRepresentation instance = new CustomListRepresentation();
private CustomListRepresentation() {
}
@Override
public String toStringOf(Object) {
List<?> list = (List<?>) object;
return list.stream()
.filter(Obj.class::isInstance)
.map(Obj.class::cast).map(Obj::getId)
.map(String::valueOf)
.collect(Collectors.joining(","));
}
@Override
public String unambiguousToStringOf(Object object) {
return this.toStringOf(object);
}
}
如果除 Obj
之外的多个 classes 也有此问题,那么您可以保留这些 classes 的映射及其映射函数(用于代替 toString()
) 可以定义并适当地替换 .filter(Obj.class::isInstance).map(Obj.class::cast).map(Obj::getId)
。
现在assert语句需要修改为:
assertThat(list).withRepresentation(CustomListRepresentation.instance)
.containsExactly(item1, item2);
或者另一种方法是使用 overridingErrorMessage
方法覆盖错误消息:
private static String getError(List<Obj> list, Obj... items) {
// return a custom formatted error message.....
}
断言为:
assertThat(list).overridingErrorMessage(getError(list, item1, item2))
.containsExactly(item1, item2);
根据
Representation pres = new StandardRepresentation() {
public String toStringOf(Object object) {
return unambiguousToStringOf(object);
}
public String unambiguousToStringOf(Object object) {
return (object == null) ? "null" : object.getClass().getSimpleName() + "@" + object.hashCode();
}
};
在我的测试中就像这样(用一个愚蠢的例子class):
@ToString
public static class A {
public A a;
}
@Test
void testNoError() {
var a = new A();
a.a = a;
// Not null so AssretJ prints out failing object when test fails
assertThat(a).withRepresentation(pres).isNull();
}