Hot questions for Using Mockito in mongodb

Question:

I want to mock a method with signature as:

    public <T> T documentToPojo(Document mongoDoc, Class<T> clazz)

I mock it as below:

    Mockito.when(mongoUtil.documentToPojo(Mockito.any(Document.class), Mockito.any(WorkItemDTO.class)))

But I get error as:

The method documentToPojo(Document, Class<T>) in the type MongoUtil is not applicable for the arguments (Document, WorkItemDTO)

Is there any method in Mockito which will help me mock for T?


Answer:

Note that documentToPojo takes a Class as its second argument. any(Foo.class) returns an argument of type Foo, not of type Class<Foo>, whereas eq(WorkItemDTO.class) should return a Class<WorkItemDTO> as expected. I'd do it this way:

when(mongoUtil.documentToPojo(
    Mockito.any(Document.class),
    Mockito.eq(WorkItemDTO.class))).thenReturn(...);

Question:

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


Answer:

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.

Question:

I am writing a junit test case for testing the rest calls.

I tried to mock the ticket service, it works fine but when i mock it in the REST service call. it does not mock.

I am using springboot, mongodb with REST.

Any suggestions to solve this problem ?

@RestController
@RequestMapping("/ticket")
public class TicketRestController 
{
    @Autowired
    public TicketService ticketService;

    @RequestMapping (path = "/all", method = {RequestMethod.GET})
    public List<Ticket> getAllTicket() 
    {
        return ticketService.getAll();
    }
}


public interface TicketService
{

    public List<Ticket> getAll();
}


@Service
public class TicketServiceImpl implements TicketService {

  @Autowired
  TicketRepository ticketRepository;

  public List<Ticket> getAll() {
    return ticketRepository.findAll();
  }
} 



 public interface TicketRepository extends MongoRepository<Ticket, String>                            {

    public List<Ticket> findAll();

 }

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/mongo-repository-context.xml")
@WebAppConfiguration
public class TicketControllerTest extends AbstractTicketTest {

public static final String PATH = "/ticket";

public static final String ALL = PATH + "/all";

public static final String ID = PATH + "/id";

public static final String STATE = PATH + "/state";

public static final String PAYMENT_TYPE = PATH + "/paymentType";

public static final String TABLE_NUMBER = PATH + "/tableNumber";

@Autowired
private WebApplicationContext ctx;

private MockMvc mockMvc;

@Autowired
@InjectMocks
private TicketService ticketService;

@Mock
private TicketRepository ticketRepository;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
    ticketRepository.deleteAll();
}

@Test
public void getAllTickets() throws Exception {
    Mockito.when(ticketRepository.findAll()).thenReturn(TicketMockProvider.createTickets());

    this.mockMvc.perform(get(ALL))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.*", hasSize(1)))
            .andExpect(jsonPath("$[0].ticketItems", hasSize(2)));
  }

}


Answer:

The problem is that the TicketRepository used in your TicketService is not the one mocked by mockito.

The one in your test class is instanciated by Mockito itself, whereas the one in your TicketService is instanciated by Spring.

You could make it work by changing your init method:

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
    ticketRepository.deleteAll();
    // new code starts here
   ticketService.setTicketRepository(ticketRepository); // this method needs to be created.
}

This way, your TicketService instance will use the mocked ticketRepository.

Question:

I'm trying to mock the following piece of code in my JUnit test

requestData = requestRepository.findByRequestId(requestId);

by doing

@Mock
RequestRepository requestRepository;

@Mock
RequestData requestData;

Mockito.when(requestRepository.findByRequestId(requestId)).thenReturn(requestData);

But instead of giving the mock object back, i'm getting null value. What is the correct way to mock MongoDB repository methods.


Answer:

When using SpringRunner based test, use @MockBean to declare mock of your context beans.

Question:

Im using mockito, and one of the mock is of service that have a method with return type Future[DeleteResult], and in my test i have something like:

val myService = mock[MyService]

when(myService.doSomething(5)) thenReturn Future.successful(???)

but i dont know how to imitate DeleteResult...


Answer:

Simple you can initiate it like so:

import com.mongodb.client.result.DeleteResult
DeleteResult.acknowledged(1)

Question:

The unit test below is not able to mock DeleteResult for the Java code being tested. Getting a NullPointerException. I'm running the test on JUnit. Is it something to do with the Filters in the delete statement?

    @InjectMocks
    DBConnection mongoConnect;

    @Mock
    MongoClient mockClient;

    @Mock
    MongoCollection<Document> mockCollection;

    @Mock
    MongoDatabase mockDB;

    @Mock
    LinkedList<String> mockArrList;

    @Mock
    MongoIterable<String> mongoIter;

    @Mock
    DeleteResult mockDeleteResult;

    @SuppressWarnings("unchecked")
    @Test
    public void deleteDocTest1() {

        Mockito.when(mockClient.getDatabase(Mockito.anyString())).thenReturn(mockDB);       

        MongoIterable<String> mongoIter = Mockito.mock(MongoIterable.class);
        Mockito.when(mockDB.listCollectionNames()).thenReturn(mongoIter);       
        Mockito.when(mongoIter.into(new LinkedList<String>())).thenReturn(mockArrList);
        Mockito.when(mockArrList.contains(Mockito.anyString())).thenReturn(true);   
        Mockito.when(mockDB.getCollection(Mockito.anyString())).thenReturn(mockCollection);
        Mockito.when(mockCollection.deleteOne(Filters.and(Filters.eq("aid", "TS123"), 
                Filters.eq("year", "2018"),
                Filters.eq("position", "testCases"))))
                .thenReturn(mockDeleteResult);
        Mockito.when(mockDeleteResult.getDeletedCount()).thenReturn(1L);

        String msg = mongoConnect.deleteDocument("TS123", "testCases", "2018");
        assertEquals("Delete Successful", msg);     

    }

The code being tested just has to delete a record if the keys match, and return a warning if there is no such record. The method below, which is being tested, is part of DBCollection class:

public String deleteDocument(String aId, String collection, String year) {

        MongoDatabase database = mongoClient.getDatabase(databaseName);

        //checking if collection is present in the DB
        boolean collectionExists = database.listCollectionNames().into(new LinkedList<String>())
                .contains(collection);

        if(collectionExists) {
            MongoCollection<Document> collectionDocs = database.getCollection(collection);
            System.out.println(assoId+" "+collection+" "+year);         
            DeleteResult deleteResult = collectionDocs.deleteOne(Filters.and(Filters.eq("aid", aId), Filters.eq("year",year), Filters.eq("position",collection)));
            if(deleteResult.getDeletedCount() == 0) //the ERROR is at this line
                return "Delete: record does not exist";
        }else {
            return "Delete: record does not exist";
        }
        mongoClient.close();
        return "Successful Delete"; 

    }   

The stack trace for the error:

java.lang.NullPointerException
    at com.repo.repository.DBConnection.deleteDocument(DBConnection.java:103)
    at com.repo.form_upload.test.DBTest.deleteDocTest1(DBTest.java:138)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:16)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

Any ideas on what's the problem?


Answer:

The issue here is with this expectation:

Mockito.when(mockCollection.deleteOne(Filters.and(Filters.eq("aid", "TS123"),
        Filters.eq("year", "2018"),
        Filters.eq("position", "testCases"))))
        .thenReturn(mockDeleteResult);

Bson does not implement equals so when Mockito attempts to determine whether it should return something from the collectionsDocs.deleteOne call in your deleteDocument it cannot match the filter argument so it determines that collectionsDocs.deleteOne returns nothing. To verify this, just run the following code:

Bson one = Filters.and(Filters.eq("aid", "TS123"),
        Filters.eq("year", "2018"),
        Filters.eq("position", "testCases"));
Bson two = Filters.and(Filters.eq("aid", "TS123"),
        Filters.eq("year", "2018"),
        Filters.eq("position", "testCases"));

// one and two are not equal because Bson does not implement equals so 
// we'll just fall back to the standard instance check in Object
assertNotEquals(one, two);

Your test will pass - albeit with less specificity about the filters - if you express the deleteOne expectation like this:

Mockito.when(mockCollection.deleteOne(any(Bson.class))).thenReturn(mockDeleteResult);

Alternatively you could use a custom matcher to apply your own equals check on the Bson. For example, you would change the mockCollection.deleteOne expectation to the following:

Mockito.when(mockCollection.deleteOne(argThat(new BsonMatcher(Filters.and(Filters.eq("aid", "TS123"),
        Filters.eq("year", "2018"),
        Filters.eq("position", "testCases"))))))
        .thenReturn(mockDeleteResult);

And declare the BsonMatcher as follows:

public class BsonMatcher implements ArgumentMatcher<Bson> {

    private BsonDocument left;

    public BsonMatcher(Bson left) {
        this.left = left.toBsonDocument(BsonDocument.class, MongoClient.getDefaultCodecRegistry());
    }

    @Override
    public boolean matches(Bson right) {
        // compare as BsonDocument, since this does provide an equals()
        return left.equals(right.toBsonDocument(BsonDocument.class, MongoClient.getDefaultCodecRegistry()));
    }
}

Note you'll also need to change your assertEquals("Delete Successful", msg); to assertEquals("Successful Delete", msg); because deleteDocument returns "Successful Delete" :)

Question:

I'm currently building a REST api in Scala which interfaces with a Mongo database. The api action in question creates users in a "users" collection.

I'm trying to cover an issue with a unit test where the database driver throws a DatabaseException if I attempt to create a record which violates a unique key constraint. Using Mockito, I have this so far:

describe("a mongo db error") {

    val collection = mockCollection(Some("users"))

    doThrow(GenericDatabaseException("Test exception", None))
      .when(collection)
      .insert(any(), any())(any(), any())

    val userRequest = CreateUserRequest("test", "test", "test")
    val request = FakeRequest().withJsonBody(Json.toJson(userRequest))
    val result = call(controller.post, request)
    val response = Json.fromJson[GenericResponse](contentAsJson(result)).get

    it("should return a bad request") {
      response.status must be("Failed")
    }
  }

This is the api method under test:

def post = Action.async(parse.json) { implicit request =>
request.body.validate[CreateUserRequest].map {
  case model => {
    collection flatMap { c =>

      val hashedPassword = SecureHash.createHash(model.password)

      c.insert(User(model.username, hashedPassword, model.emailAddress)) flatMap { r =>
        c.indexesManager.ensure(Index(List(("username", IndexType.Ascending)), unique = true)) map { r =>
          Ok
        }
      } recover {
          case dex: DatabaseException => BadRequest(Json.toJson(GenericResponse("Failed")))

      }
    }

  }
}.recoverTotal { e =>

  val errorResponse = BadRequest(Json.obj(
    "status" -> Messages("status.invalid"),
    "message" -> Messages("error.generic.invalid_request")))

  Future.successful(errorResponse)
}

The error I'm getting when running the tests is this: Checked exception is invalid for this method and, from my limited knowledge of Scala, Java and how exception handling works, I understand that methods have to declare the exceptions they expect to throw, which is why this error might occur.

How can I move forward from here and test this scenario? For what it's worth, the api method works as expected under manual testing.


Answer:

You'll have to resort to using Answer in this case. Here's an example from REPL:

import org.mockito.Matchers.{eq => exact, _}
import org.mockito.Mockito._
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
import org.scalatest.mock.MockitoSugar

trait MyService {
  def insert(v: String): String
}

val mk = MockitoSugar.mock[MyService]

when(mk.insert(any())).thenAnswer(new Answer[String] {
  def answer(invocation: InvocationOnMock): String =
    throw new Exception("this should have never happened")
})

mk.insert("test")
// java.lang.Exception: this should have never happened
//     at #worksheet#.$anon$1.answer(/dummy.sc:14)
//     at #worksheet#.$anon$1.answer(/dummy.sc:13)
//     at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(/dummy.sc:30)
//     at #worksheet#.#worksheet#(/dummy.sc:87)

Edit: In our project we defined a set of implicit conversions from FunctionN to Answer so there's less boilerplate in such cases, like the following:

implicit def function1ToAnswer[T, R](function: T => R)(implicit ct: ClassTag[T]): Answer[R] = new Answer[R] {
  def answer(invocation: InvocationOnMock): R = invocation.getArguments match {
    case Array(t: T, _*) => function(t)
    case arr => fail(s"Illegal stubbing, first element of array ${arr.mkString("[", ",", "]")} is of invalid type.")
  }
}

Edit 2: As for working with Futures in Mockito, considering their almost core language feature semantics, here's another very convenient wrapper I invented to simplify unit-testing:

implicit class ongoingStubbingWrapperForOngoingStubbingFuture[T](stubbing: OngoingStubbing[Future[T]]) {
  def thenReturn(futureValue: T): OngoingStubbing[Future[T]] = stubbing.thenReturn(Future.successful(futureValue))
  def thenFail(throwable: Throwable): OngoingStubbing[Future[T]] = stubbing.thenReturn(Future.failed(throwable))
}

thenReturn is straightforward and transparent against the original method (even allows you to convert existing synchronous code to asynchronous with less fixes in tests). thenFail is a little less so, but we are unable to define thenThrow for this case - the implicit won't be applied.