Mockito验证交互与验证结果

时间:2017-11-22 09:54:48

标签: java unit-testing mockito

Mockito在verify方法上的JavaDocs链接到this interesting article关于询问和告知。我在那里特意迷失了“存根互动被隐含地验证”。

我们举一个例子:

想象一下有这个班级

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查询。
  • 参数已正确绑定到查询对象。
  • 结果就是我的期望。

首先,我将模拟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查询已正确构建,第二个验证将确保参数已正确绑定到查询。这些验证是否必要或只是断言结果是否足够?

1 个答案:

答案 0 :(得分:3)

您必须与entityManager存根交互,否则您的测试会在findFooByName()调用setParameter()getQueryList()

时导致NPE被抛出

关于是否存根或验证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):

  

结果就是我的期望。

     

参数正确绑定到查询对象。

     

为给定的参数正确构造HQL查询。

@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);
}

因此,总而言之,在确定是否包含验证或存根交互时,您可能还需要考虑:

  • 被测代码的作用:
    • 可能需要进行存根以防止暂停执行
    • 使用验证
    • 可以充分证明除委托之外的任何其他方法
    • 从模拟转换响应的方法可能需要存根,然后断言转换后的响应
  • 您希望测试用例的具体程度如何:
    • 某些测试路径需要验证以提供全面覆盖
    • Stubbing +验证可能有点过分;检查您的代码覆盖率数字,确定其他验证调用是否会增加收益或仅为您的测试用例添加混乱