Search code examples
javaunit-testingmockito

Mockito verify interaction vs verify result


Mockito's JavaDocs on verify methods link to this interesting article about asking and telling. I'm a bit lost there specially with the "stubbed interactions are verified implicitly".

Let's take an example:

Imagine a have this class

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

Now, let's see what can I check about this method:

  • That the HQL query is constructed properly for the parameters given.
  • That the parameters are correctly bound to the query object.
  • That the result is what I expect.

First of all I will mock the EntityManager object because I don't want to access a real database.

@InjectMocks
private FooDao dao;

@Mock
private EntityManager entityManager;

Now, I could just add something like this:

@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?
}

So, now the question is what to check. I could just add an assertEquals(stubData, actual) and the test would succeed.

I could also add:

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

The first verification will ensure that the HQL query has been constructed properly and the second one will ensure that the parameter was correctly bound to the query. Are these verifications necessary at all or just asserting the result is enough?


Solution

  • You have to stub the interaction with entityManager since otherwise your test would cause a NPE to be thrown when findFooByName() invokes setParameter() or getQueryList()

    The choice about whether to stub or verify the query.getResultList() call comes down to how specific you want your test to be ...

    Least Specific

    The following test is non specific about how the TypedQuery is created and instead satisfies itself that it is somehow created and that its getResultList() method is invoked.

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

    More Specific

    The following test is non specific about how the TypedQuery is created and instead verifies that it is somehow created and that the result of its getResultList() call is returned by the method-under-test.

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

    Most Specific

    The following test proves all of the following (from your 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);
    }
    

    So, in summary, when determining whether to include verifications or stubbed interactions or both you likely want to consider:

    • What the code-under-test does:
      • Stubbing may be required to prevent halted execution
      • A method which does nothing other than delegate could be adequately proven using a verification
      • A method which transforms the response from a mock would likely require stubbing followed by an assertion against the transformed response
      • etc
    • How specific you want your test case to be:
      • Some test paths will require verification to provide full coverage
      • Stubbing + verification might be overkill; review your code coverage numbers, determine whether additional verification calls are adding benefit or just adding clutter to your test case