【问题标题】:How do I assert an Iterable contains elements with a certain property?如何断言 Iterable 包含具有特定属性的元素?
【发布时间】:2012-08-28 19:43:20
【问题描述】:

假设我想用这个签名对一个方法进行单元测试:

List<MyItem> getMyItems();

假设MyItem 是一个具有许多属性的Pojo,其中一个是"name",通过getName() 访问。

我关心的只是验证List&lt;MyItem&gt; 或任何Iterable 包含两个MyItem 实例,它们的"name" 属性具有值"foo""bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试成功。

如果可能的话,我希望它是单行的。这是我想做的那种事情的一些“伪语法”。

assert(listEntriesMatchInAnyOrder(myClass.getMyItems(), property("name"), new String[]{"foo", "bar"});

Hamcrest 对这类事情有好处吗?如果是这样,我上面的伪语法的 hamcrest 版本到底是什么?

【问题讨论】:

    标签: java unit-testing junit4 hamcrest


    【解决方案1】:

    感谢@Razvan 为我指明了正确的方向。我能够在一行中得到它,并且我成功地为 Hamcrest 1.3 找到了进口。

    进口:

    import static org.hamcrest.CoreMatchers.is;
    import static org.hamcrest.Matchers.contains;
    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
    

    代码:

    assertThat( myClass.getMyItems(), contains(
        hasProperty("name", is("foo")), 
        hasProperty("name", is("bar"))
    ));
    

    【讨论】:

      【解决方案2】:

      试试:

      assertThat(myClass.getMyItems(),
                                hasItem(hasProperty("YourProperty", is("YourValue"))));
      

      【讨论】:

      • 只是作为一个侧节点 - 这是一个 hamcrest 解决方案(不是 assertj)
      【解决方案3】:

      它不是特别是 Hamcrest,但我认为这里值得一提。我在 Java8 中经常使用的是这样的:

      assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));
      

      (根据 Rodrigo Manyari 的轻微改进进行了编辑。它不那么冗长。参见 cmets。)

      它可能有点难以阅读,但我喜欢这种类型和重构安全性。 组合测试多个 bean 属性也很酷。例如在过滤器 lambda 中使用类似 java 的 && 表达式。

      【讨论】:

      • 轻微改进:assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName()));
      • @RodrigoManyari,右括号丢失
      • 此解决方案浪费了显示适当错误消息的可能性。
      • @GiulioCaccin 我认为不会。如果您使用 JUnit,您可以/应该使用重载的断言方法并编写 assertTrue(..., "My own test failure message");查看更多关于junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
      • 我的意思是,如果您对布尔值进行断言,您将失去自动打印实际/预期差异的能力。可以使用匹配器进行断言,但您需要将此响应修改为与此页面中的其他响应类似。
      【解决方案4】:

      AssertJ 在extracting() 中提供了一个出色的功能:您可以通过Functions 来提取字段。它在编译时提供检查。
      您也可以轻松地先断言大小。

      它会给:

      import static org.assertj.core.api.Assertions;
      
      Assertions.assertThat(myClass.getMyItems())
                .hasSize(2)
                .extracting(MyItem::getName)
                .containsExactlyInAnyOrder("foo", "bar"); 
      

      containsExactlyInAnyOrder() 断言列表只包含这些值,无论顺序如何。

      要断言列表包含这些值,无论顺序如何,但也可能包含其他值,请使用contains()

      .contains("foo", "bar"); 
      

      附带说明:要从 List 的元素断言多个字段,使用 AssertJ,我们通过将每个元素的预期值包装到 tuple() 函数中来做到这一点:

      import static org.assertj.core.api.Assertions;
      import static org.assertj.core.groups.Tuple;
      
      Assertions.assertThat(myClass.getMyItems())
                .hasSize(2)
                .extracting(MyItem::getName, MyItem::getOtherValue)
                .containsExactlyInAnyOrder(
                     tuple("foo", "OtherValueFoo"),
                     tuple("bar", "OtherValueBar")
                 ); 
      

      【讨论】:

      • 不明白为什么这没有赞成票。我认为,这是迄今为止最好的答案。
      • assertJ 库比 JUnit 断言 API 更具可读性。
      • @Sangimed 同意,而且我更喜欢它而不是 hamcrest。
      • 在我看来,它的可读性略差,因为它将“实际值”与“预期值”分开,并将它们按需要匹配的顺序排列。
      【解决方案5】:

      Assertj 擅长这个。

      import static org.assertj.core.api.Assertions.assertThat;
      
          assertThat(myClass.getMyItems()).extracting("name").contains("foo", "bar");
      

      与 hamcrest 相比,assertj 的一大优点是代码补全的易用性。

      【讨论】:

      • 另一种方式弗兰克:assertThat(list) .containsAll(Arrays.asList(id1,id2));
      【解决方案6】:

      只要你的 List 是一个具体的类,只要你在 MyItem 上实现了 equals() 方法,你就可以简单地调用 contains() 方法。

      // given 
      // some input ... you to complete
      
      // when
      List<MyItems> results = service.getMyItems();
      
      // then
      assertTrue(results.contains(new MyItem("foo")));
      assertTrue(results.contains(new MyItem("bar")));
      

      假设您已经实现了一个构造函数,该构造函数接受您要断言的值。我意识到这不是在一行中,但是知道缺少哪个值而不是同时检查两个值很有用。

      【讨论】:

      • 我真的很喜欢你的解决方案,但他应该修改所有代码进行测试吗?
      • 我认为这里的每个答案都需要一些测试设置,执行测试方法,然后断言属性。从我所见,我的答案并没有真正的开销,只是我在单独的行上有两个断言,因此失败的断言可以清楚地确定缺少什么值。
      • 最好在 assertTrue 中也包含一条消息,这样错误消息就更容易理解了。如果没有消息,如果它失败,JUnit 只会抛出一个 AssertionFailedError 而没有任何错误消息。所以最好包括“结果应该包含新的 MyItem(\"foo\")”之类的内容。
      • 是的,你是对的。无论如何我都会推荐 Hamcrest,而且这些天我从不使用 assertTrue()
      • 附带说明您的 POJO 或 DTO 应该定义 equals 方法
      【解决方案7】:

      AssertJ 3.9.1 支持在anyMatch 方法中直接使用谓词。

      assertThat(collection).anyMatch(element -> element.someProperty.satisfiesSomeCondition())
      

      这通常适用于任意复杂条件的用例。

      对于简单的条件,我更喜欢使用extracting 方法(见上文),因为生成的iterable-under-test 可能支持具有更好可读性的值验证。 示例:它可以提供专门的 API,例如 Frank Neblung 的答案中的 contains 方法。或者您可以稍后调用anyMatch 并使用方法参考,例如"searchedvalue"::equals。还可以将多个提取器放入extracting 方法中,随后使用tuple() 验证结果。

      【讨论】:

        【解决方案8】:

        除了hasProperty,您可以尝试hamcrest-more-matchers where 具有提取功能的匹配器。在您的情况下,它看起来像:

        import static com.github.seregamorph.hamcrest.MoreMatchers.where;
        
        assertThat(myClass.getMyItems(), contains(
            where(MyItem::getName, is("foo")), 
            where(MyItem::getName, is("bar"))
        ));
        

        这种方法的优点是:

        • 如果值是在 get 方法中计算的,则并非总是可以按字段验证
        • 如果不匹配,应该会显示带有诊断的失败消息(注意已解决的方法参考 MyItem.getName
        Expected: iterable containing [Object that matches is "foo" after call
        MyItem.getName, Object that matches is "bar" after call MyItem.getName]
             but: item 0: was "wrong-name"
        
        • 它适用于 Java 8、Java 11 和 Java 14

        【讨论】:

          【解决方案9】:

          使用 Stream,您还可以:

          List<String> actual = myList.stream().map(MyClass::getName).collect(toList());
          assertThat(actual, hasItem("expectedString1"));
          

          因为使用anyMatch()allMatch(),您知道列表中的一些 值在列表中,但您的实际列表可能只包含5 个值而anyMatch() 您有6个;您不知道是否存在 所有 值。使用hasItem(),您确实可以检查您想要的每个值。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-08-23
            • 2020-07-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多