Hamcrest:测试列表包含一个具有特定值的私有字段的项目
Hamcrest: test list contains an item that has a private field with a certain value
我有这个class:
public class A {
private int x;
public A(int x) {
this.x = x;
}
}
和一个不同的方法class我想测试:
public class B {
public List underTest() {
int x = doStuff();
return Collections.singletonList(new A(x));
}
private int doStuff() { /* ... */ }
}
我想测试一下,在 underTest()
的末尾,returned 列表中的项目包含一个等于特定值的 x
字段。我写了这个:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
@Test
public void Test1() {
List result = bInstance.underTest();
assertThat(result, contains(hasProperty("x", is(1))));
}
但是 junit 抱怨我的测试用例 item 0: No Property "x"
。
我该如何测试?我唯一能想到的是为 getX()
添加 public getter,然后遍历 returned List
并检查每个元素。请记住,虽然方法 return 是 singletonList
,但 return 类型只是 List
,因此它 可以 在未来有多个值。
我认为首先值得一提的是,像这样测试 class 内部结构不是一个好主意,除非在非常特殊的情况下。您的测试将很脆弱,通常完全安全的更改(即重命名字段)现在可能会导致您的自动构建失败。您应该测试外部行为,而不是实施细节。
看来你最好在 class A
中实现 equals
和 hashCode
,这样你就可以简单地做:
contains(new A(1))
话虽如此,如果您确实有充分的理由这样做(这种情况很少见),那么您不能为此使用 hasProperty
。
来自 the JavaDoc:
Creates a matcher that matches when the examined object has a JavaBean
property with the specified name whose value satisfies the specified
matcher.
我相信这意味着您需要一个名为 getX
.
的方法
您不应仅仅为了测试目的而添加这样的方法,但您可以编写自己的通用 Matcher
实现,该实现将使用反射来检查 class.
这是一个示例实现:
class ReflectiveFieldMatcher<T> extends BaseMatcher<Object>
{
private final String fieldName;
private final T expectedValue;
ReflectiveFieldMatcher(final String fieldName, final T expectedValue)
{
this.fieldName = fieldName;
this.expectedValue = expectedValue;
}
@Override
public boolean matches(final Object obj)
{
for (final Field field : obj.getClass().getFields())
{
if (field.getName().equals(fieldName))
{
field.setAccessible(true);
try
{
Object value = field.get(obj);
return expectedValue.equals(value);
}
catch (final IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
}
return false;
}
@Override
public void describeTo(final Description description)
{
description.appendText("Object with field '" + fieldName + "' with value: " + expectedValue);
}
}
您的示例现在看起来像这样:
assertThat(result, contains(new ReflectiveFieldMatcher<>("x", 1)));
我有这个class:
public class A {
private int x;
public A(int x) {
this.x = x;
}
}
和一个不同的方法class我想测试:
public class B {
public List underTest() {
int x = doStuff();
return Collections.singletonList(new A(x));
}
private int doStuff() { /* ... */ }
}
我想测试一下,在 underTest()
的末尾,returned 列表中的项目包含一个等于特定值的 x
字段。我写了这个:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
@Test
public void Test1() {
List result = bInstance.underTest();
assertThat(result, contains(hasProperty("x", is(1))));
}
但是 junit 抱怨我的测试用例 item 0: No Property "x"
。
我该如何测试?我唯一能想到的是为 getX()
添加 public getter,然后遍历 returned List
并检查每个元素。请记住,虽然方法 return 是 singletonList
,但 return 类型只是 List
,因此它 可以 在未来有多个值。
我认为首先值得一提的是,像这样测试 class 内部结构不是一个好主意,除非在非常特殊的情况下。您的测试将很脆弱,通常完全安全的更改(即重命名字段)现在可能会导致您的自动构建失败。您应该测试外部行为,而不是实施细节。
看来你最好在 class A
中实现 equals
和 hashCode
,这样你就可以简单地做:
contains(new A(1))
话虽如此,如果您确实有充分的理由这样做(这种情况很少见),那么您不能为此使用 hasProperty
。
来自 the JavaDoc:
Creates a matcher that matches when the examined object has a JavaBean property with the specified name whose value satisfies the specified matcher.
我相信这意味着您需要一个名为 getX
.
您不应仅仅为了测试目的而添加这样的方法,但您可以编写自己的通用 Matcher
实现,该实现将使用反射来检查 class.
这是一个示例实现:
class ReflectiveFieldMatcher<T> extends BaseMatcher<Object>
{
private final String fieldName;
private final T expectedValue;
ReflectiveFieldMatcher(final String fieldName, final T expectedValue)
{
this.fieldName = fieldName;
this.expectedValue = expectedValue;
}
@Override
public boolean matches(final Object obj)
{
for (final Field field : obj.getClass().getFields())
{
if (field.getName().equals(fieldName))
{
field.setAccessible(true);
try
{
Object value = field.get(obj);
return expectedValue.equals(value);
}
catch (final IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
}
return false;
}
@Override
public void describeTo(final Description description)
{
description.appendText("Object with field '" + fieldName + "' with value: " + expectedValue);
}
}
您的示例现在看起来像这样:
assertThat(result, contains(new ReflectiveFieldMatcher<>("x", 1)));