java - Mockito 验证交互与验证结果

标签 java unit-testing mockito

关于 verify 方法的 Mockito JavaDocs 链接到 this interesting article关于询问和讲述。我对“ stub 交互隐式验证”有点迷茫。

举个例子:

想象一下有这个类

class FooDao {
    private EntityManager entityManager;

    public FooDao(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public List<Foo> findFooByName(String name, String otherParam) {
        String hql = "SELECT f FROM Foo f WHERE 1 = 1 ";
        if(name != null) {
            hql += " AND f.name = :name";
        }
        if(otherParam != null) {
            hql += " AND f.other = :otherParam";
        }
        TypedQuery<Foo> query = this.entityManager.createQuery(hql, Foo.class);

        if(name != null) {
            query.setParameter("name", name);
        }
        if(otherParam != null) {
            query.setParameter("otherParam", otherParam);
        }
        return query.getResultList();
    }
}

现在,让我们看看我可以检查此方法的哪些内容:

  • 针对给定的参数正确构建了 HQL 查询。
  • 参数正确绑定(bind)到查询对象。
  • 结果是我所期望的。

首先,我将模拟 EntityManager 对象,因为我不想访问真实的数据库。

@InjectMocks
private FooDao dao;

@Mock
private EntityManager entityManager;

现在,我可以添加如下内容:

@Mock
private TypedQuery<Foo> mockQuery;

@Test
public void testFindFooByName() {
    List<Foo> stubData = stubData(); // Method definition omitted for brevity 
    // Return the custom mockedQuery with the entityManager
    Mockito.when(mockQuery.getResultList()).thenReturn(stubData);
    Mockito.when(entityManager.createQuery(Mockito.anyString(), 
                    Mockito.eq(Foo.class)))
            .thenReturn(mockQuery);
    List<Foo> actual = dao.findFooByName("foobar", null);

    // Now what should I check?
}

所以,现在的问题是要检查什么。我可以只添加一个 assertEquals(stubData, actual) 并且测试会成功。

我还可以添加:

Mockito.verify(entityManager).createQuery(expectedSql, Foo.class);
Mockito.verify(mockQuery).setParameter("name", "foobar");

第一个验证将确保 HQL 查询已正确构建,第二个验证将确保参数已正确绑定(bind)到查询。这些验证是完全必要的还是仅断言结果就足够了?

最佳答案

您必须 stub 与 entityManager 的交互,否则您的测试将导致在 findFooByName() 调用 setParameter() 时抛出 NPE > 或 getQueryList()

关于是 stub 还是验证 query.getResultList() 调用的选择取决于您希望测试的具体程度...

最不具体

下面的测试没有具体说明 TypedQuery 是如何创建的,而是让自己确信它是以某种方式创建的,并且它的 getResultList() 方法被调用。

@Test
public void testFindFooByName() {
    List<Foo> stubData = stubData();

    Mockito.when(entityManager.createQuery(Mockito.anyString(), Mockito.eq(Foo.class))).thenReturn(mockQuery);

    dao.findFooByName("foobar", null);

    Mockito.verify(mockQuery).getResultList();
}

更具体

以下测试未具体说明 TypedQuery 是如何创建的,而是验证它是以某种方式创建的,以及 getResultList()< 的结果 调用由被测方法返回。

@Test
public void testFindFooByName() {
    List<Foo> stubData = stubData();

    Mockito.when(mockQuery.getResultList()).thenReturn(stubData);
    Mockito.when(entityManager.createQuery(Mockito.anyString(), Mockito.eq(Foo.class))).thenReturn(mockQuery);

    List<Foo> actual = dao.findFooByName("foobar", null);

    Assert.assertSame(stubData, actual);
}

最具体

以下测试证明了以下所有内容(来自您的 OP):

That the result is what I expect.

That the parameters are correctly bound to the query object.

That the HQL query is constructed properly for the parameters given.

@Test
public void testFindFooByName() {
    String name = "foobar";
    String otherParam = "otherParam";

    String expectedHql = "SELECT f FROM Foo f WHERE 1 = 1 AND f.name = :name AND f.other = :otherParam";

    List<Foo> stubData = stubData();

    Mockito.when(mockQuery.getResultList()).thenReturn(stubData);
    Mockito.when(entityManager.createQuery(Mockito.eq(expectedHql), Mockito.eq(Foo.class))).thenReturn(mockQuery);

    List<Foo> actual = dao.findFooByName(name, otherParam);

    Assert.assertSame(stubData, actual);

    Mockito.verify(mockQuery).setParameter("name", name);
    Mockito.verify(mockQuery).setParameter("otherParam", otherParam);
}

因此,总而言之,在确定是否包括验证或 stub 交互或两者时,您可能需要考虑:

  • 被测代码的作用:
    • 可能需要 stub 以防止暂停执行
    • 除了委托(delegate)之外什么都不做的方法可以使用验证来充分证明
    • 从模拟中转换响应的方法可能需要 stub ,然后对转换后的响应进行断言
    • 等等
  • 您希望测试用例的具体程度:
    • 一些测试路径需要验证以提供全面覆盖
    • stub + 验证可能有点矫枉过正;检查您的代码覆盖率数字,确定额外的验证调用是否增加了好处或只是给您的测试用例增加了困惑

关于java - Mockito 验证交互与验证结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47431517/

相关文章:

java - 无法更新 arrayList 中的元素

visual-studio - 在启动项目进行调试时,为什么 Visual Studio 构建整个解决方案,而不仅仅是项目?

Android Mockito 如何模拟资源

java - mockito 测试对抽象执行器的调用

PHPUnit 和 Selenium 示例 17.1

java - void 方法应使用哪个测试替身

java - 如何在不覆盖的情况下向标签添加文本?

java - 多线程——匹配实例

java - 多部分/表单数据发布 - Java Spring

java - 在不继承抽象类的情况下模拟对抽象类的公共(public)方法的调用,最好使用 mockito