Java Hamcrest:测试列表包含一个具有特定值的私有字段的项

Java Hamcrest:测试列表包含一个具有特定值的私有字段的项,java,unit-testing,junit,hamcrest,Java,Unit Testing,Junit,Hamcrest,我有这门课: public class A { private int x; public A(int x) { this.x = x; } } 我想测试另一个类中的一个方法: public class B { public List underTest() { int x = doStuff(); return Collections.singletonList(new A(x)); } pr

我有这门课:

public class A {
    private int x;

    public A(int x) {
        this.x = x;
    }
}
我想测试另一个类中的一个方法:

public class B {
    public List underTest() {
        int x = doStuff();
        return Collections.singletonList(new A(x));
    }

    private int doStuff() { /* ... */ }
}
我想测试一下,在
underTest()
的末尾,返回列表中的项包含一个等于某个值的
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抱怨说,对于我的测试用例,
item0:No属性“x”


我如何测试这个?我唯一能想到的就是为
getX()
添加一个公共getter,然后遍历返回的
列表
并检查每个元素。请记住,虽然该方法返回一个
singletonList
,但返回类型只是
List
,因此将来可以将其更改为包含多个值。

我认为首先值得一提的是,像这样测试类内部结构不是一个好主意,除非是在非常特殊的情况下。您的测试将是脆弱的,通常是完全安全的更改(即重命名字段)现在可能会导致您的自动构建失败。您应该测试外部行为,而不是实现细节

似乎您最好在类
A
中实现
equals
hashCode
,这样您就可以简单地执行以下操作:

contains(new A(1))
话虽如此,如果您确实有一个非常好的理由这样做(这种情况将非常罕见),那么您就不能使用
hasProperty

发件人:

创建一个匹配器,该匹配器在被检查对象具有JavaBean时匹配 具有指定名称且其值满足指定 匹配器

我相信这意味着您需要一个名为
getX
的方法

您不应该仅仅为了测试的目的而添加这样的方法,但是您可以编写自己的通用实现,该实现将使用反射来检查类的字段

下面是一个示例实现:

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);
    }
}
类ReflectiveFieldMatcher扩展了BaseMatcher
{
私有最终字符串字段名;
私人最终预期价值;
ReflectiveFieldMatcher(最终字符串fieldName,最终T expectedValue)
{
this.fieldName=字段名;
this.expectedValue=expectedValue;
}
@凌驾
公共布尔匹配(最终对象obj)
{
for(最终字段:obj.getClass().getFields())
{
if(field.getName().equals(fieldName))
{
字段。setAccessible(true);
尝试
{
对象值=field.get(obj);
返回expectedValue.equals(值);
}
捕获(最终非法访问例外e)
{
抛出新的运行时异常(e);
}
}
}
返回false;
}
@凌驾
公共无效说明(最终说明)
{
description.appendText(“具有字段“”的对象+具有值的字段名+”:“+expectedValue”);
}
}
您的示例现在如下所示:

assertThat(result, contains(new ReflectiveFieldMatcher<>("x", 1)));
assertThat(结果,包含(新的ReflectiveFieldMatcher(“x”,1));

@Michael在他们的javadoc中添加了importsHamcrest,他说该属性应该是JavaBean属性,这意味着它可能需要为字段
x
提供一个公共getter。这绝对是一个很好的观点。我实际上不喜欢这种测试机制(使用私有字段),但我想不出其他方法来实现它。我的类本质上是一个HTML生成器,我不想比较大量的HTML块,其中只有
x
属性会改变,我也不想处理DOM解析。问题是返回一个列表,其中只有一个元素需要包含有问题的值。@ewok我以前对XML做过类似的事情。对于这些测试,我经常将实际输出与测试资源文件夹中的
expected_output.xml
进行比较。听起来你最好还是做那样的事。我认为XMLUnit应该用于比较HTML(嗯,)