Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito FindIterable<Document>

Am trying to write a JUnit test case for below method, am using Mockito framework.

Method:

public EmplInfo getMetaData(String objectId) {

        objectId = new StringBuffer(objectId).reverse().toString();
        try{
            BasicDBObject whereClauseCondition = getMetaDataWhereClause(objectId);
            EmplInfo emplinfo= new EmplInfo ();
            emplinfo.set_id(objectId);
            FindIterable<Document> cursorPersonDoc = personDocCollection.find(whereClauseCondition);
            for (Document doc : cursorPersonDoc) {
                emplinfo.setEmplFirstName(doc.getString("firstname"));
                emplinfo.setEmplLastName(doc.getString("lastname"));
                break;
            }
            return emplinfo;
        }catch(Exception e){
         e.printstacktrace();
        }

Junit:

@Test
public void testGetMetaData() throws Exception {
    String docObjectId = "f2da8044b29f2e0a35c0a2b5";
    BasicDBObject dbObj = personDocumentRepo.getMetaDataWhereClause(docObjectId);
    FindIterable<Document> findIterable = null;
    Mockito.when(collection.find(dbObj)).thenReturn(findIterable);
    personDocumentRepo.getMetaData(docObjectId);
}

Am getting null point expection in "personDocumentRepo.getMetaData(docObjectId)", because am "Return" the findIterable which is NULL. Not sure how to assign dummy/test value into findIterable.

Please advise.

Thanks! Bharathi

like image 832
Bharathiraja S Avatar asked Jun 22 '18 12:06

Bharathiraja S


3 Answers

As you have rightly pointed out, you are getting NPE because FindIterable is null. You need to mock it.
Mocking it is not so straightforward, since it uses MongoCursor(this in turn extend Iterator), you need to mock certain methods which are used internally.

While traversing certain methods of the Iter

I believe you have to do something like this.

FindIterable iterable = mock(FindIterable.class);
MongoCursor cursor = mock(MongoCursor.class);

Document doc1= //create dummy document;
Document doc2= //create dummy document;

when(collection.find(dbObj)).thenReturn(iterable);

when(iterable.iterator()).thenReturn(cursor);
when(cursor.hasNext()) 
  .thenReturn(true)
  .thenReturn(true)// do this as many times as you want your loop to traverse
 .thenReturn(false); // Make sure you return false at the end.
when(cursor.next())
  .thenReturn(doc1)
  .thenReturn(doc2); 

This is not a complete solution. You need to adapt it to your class.

like image 152
pvpkiran Avatar answered Oct 12 '22 11:10

pvpkiran


You return null in collection.find(...) mocked invocation :

FindIterable<Document> findIterable = null;
Mockito.when(collection.find(new Document())).thenReturn(findIterable);

So the mock will return null at runtime. What you need is returning a FindIterable<Document> object that allows to execute the code to test associated to :

for (Document doc : cursorPersonDoc) {
    emplinfo.setEmplFirstName(doc.getString("firstname"));
    emplinfo.setEmplLastName(doc.getString("lastname"));
    break;
}
return emplinfo;

In this way you can assert that the method does what it is designed to : setting the first name and the last name from the retrieved FindIterable<Document>.

You could use the Mockito.mock() method to mock FindIterable<Document> that is an Iterable (whereas the used foreach).
Additionally, to not bother to mock individual methods of Iterator (hasNext(), next()) that could make you test less readable, use a List (that is also an Iterable) to populate the Documents and delegate the behavior of the mocked FindIterable.iterator() to List.iterator().

@Test
public void testGetMetaData() throws Exception {
  ... 
  // add your document instances
  final List<Document> documentsMocked = new ArrayList<>();
  documentsMocked.add(new Document(...));
  documentsMocked.add(new Document(...));

  // mock FindIterable<Document>
   FindIterable<Document> findIterableMocked = (FindIterable<Document>) Mockito.mock(FindIterable.class);

  // mock the behavior of FindIterable.iterator() by delegating to List.iterator()
  when(findIterableMocked.iterator()).thenReturn(documentsMocked.iterator());

  // record a behavior for Collection.find()
  Mockito.when(collection.find(dbObj)).thenReturn(findIterableMocked);

  // execute your method to test
  EmplInfo actualEmplInfo = personDocumentRepo.getMetaData(...);

  // assert that actualEmplInfo has the expected state
  Assert(...);

}

I would add that such a mock will probably not work :

Mockito.when(collection.find(new Document())).thenReturn(findIterable);

Mockito will intercept and replace the behavior of the method invoked on the mock only if the arguments in the recording match (in terms of equals()) with the arguments passed at runtime by the tested method.
At runtime, the argument is build in this way :

BasicDBObject whereClauseCondition = getMetaDataWhereClause(objectId);
EmplInfo emplinfo= new EmplInfo ();
emplinfo.set_id(objectId);

So the argument in the mock recording should be equal to which one defined above.
Note that if equals() is not overriden/overridable for the arguments classes, you have some workarounds such as :

  • passing the object as argument in the method to test (require some refactoring). In this case, Mocked argument and the referenced passed at runtime in the method to test are necessarily equal as these refer the same object

  • matching any object of given type with Mockito.any(Class<T>). Often the simplest way but not the most robust

  • returning an Answer instead of the value to return. That is using Mockito.when(...).then(Answer) instead of Mockito.when(...).thenReturn(valuetoReturn)

like image 31
davidxxx Avatar answered Oct 12 '22 10:10

davidxxx


I would like to complete pvpkiran answer with a kind of genric method to mock a DistinctIterable (same as FindIterable).

NB/ in order to avoid sonar warning, I use inner class


    private DistinctIterable<String> mockDistinctIterableString(List<String> resultList) {
        DistinctIterable<String> iterable = mock(DistinctIterableString.class);
        MongoCursor cursor = mock(MongoCursor.class);
        doReturn(cursor)
                .when(iterable).iterator();
        OngoingStubbing<Boolean> whenHasNext = when(cursor.hasNext());
        for (String resultEntry : resultList) {
            whenHasNext = whenHasNext.thenReturn(true);
        }
        whenHasNext.thenReturn(false);
        if (CollectionUtils.isEmpty(resultList)) {
            return iterable;
        }
        OngoingStubbing<Object> whenNext = when(cursor.next());
        for (String resultEntry : resultList) {
            whenNext = whenNext.thenReturn(resultEntry);
        }
        return iterable;
    }

    public interface DistinctIterableString extends com.mongodb.client.DistinctIterable<String> {
    }

use:

        doReturn(mockDistinctIterableString(asList(
                "entry1",
                "entry2",
                "lastEntry"
        )))
        .when(myRepository)
        .myMongoDistinctQuery();
like image 31
boly38 Avatar answered Oct 12 '22 12:10

boly38