1

I'm testing an application that searches items in a MongoDB database. The application works but when I run the test, there is an error. This is the test class:

@Test
public void WhenTrovaImpegnoThenInvokeMongoCollectionFindOne(){
    String data = "01-11-2018";
    doReturn(mongoCollection).when(collection).getMongoCollection();
    doReturn(impegno).when(mongoCollection).find("{data:#}", data).as(Impegno.class);
    collection.trovaImpegno(data);
    verify(mongoCollection, times(1)).findOne("{data:#}", data).as(Impegno.class);
}

I mocked a MongoCollection object and spied the class under test:

@Spy
AgendaCollection collection;

@Mock
MongoCollection mongoCollection;

The tested method:

public Impegno trovaImpegno(String data){
    Impegno imp = new Impegno();
    imp = getMongoCollection().findOne("{data:#}", data).as(Impegno.class);
    return imp;
}

When I run the application, Impegno objects are found in the database and all works but during the test I get this error:

WhenTrovaImpegnoThenInvokeMongoCollectionFindOne(agenda.AgendaCollectionTest)  Time elapsed: 0.013 sec  <<< ERROR!
org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
String cannot be returned by find()
find() should return Find
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - 
   - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.   

I evne tried without:

doReturn(impegno).when(mongoCollection).find();

But I get a NullPointerException

Adam Kipnis
  • 10,175
  • 10
  • 35
  • 48
asso
  • 17
  • 5
  • Where do you declare `impegno` in the test? – Andy Turner Aug 19 '18 at 20:23
  • Btw, `= new Impegno();` is almost certainly unnecessary, since you overwrite its value in the next line. – Andy Turner Aug 19 '18 at 20:25
  • impegno is declared in the @Before method `@Before public void before(){ impegno = new Impegno("27-10-2018", 20 , "Cena con gli amici"); }` – asso Aug 19 '18 at 20:28
  • Then please include the `@Before` method in your question. – Andy Turner Aug 19 '18 at 20:29
  • So what is the type of `impegno`? Can't you use the standard `when(mongoCollection.find(any(), any())).thenReturn(impegno)`? – László van den Hoek Aug 19 '18 at 20:29
  • @LászlóvandenHoek i get the same error – asso Aug 19 '18 at 20:34
  • @LászlóvandenHoek , doReturn(...).when(...).method(...) is preffered over when(...).thenReturn(...) in all cases where special semantics of when-thenReturn is not needed (the actual method invocation instead of just returning the stubbed value) and the problem is not in this – nyarian Aug 19 '18 at 20:39
  • @AndreyIlyunin actually, the [JavaDoc](http://static.javadoc.io/org.mockito/mockito-core/2.21.0/org/mockito/Mockito.html#doReturn-java.lang.Object-) for `Mockito.doReturn()` states: _"Beware that `when(Object)` is always recommended for stubbing because it is argument type-safe and more readable (especially when stubbing consecutive calls)"_. So I think you have it the wrong way around w.r.t. order of preference, and perhaps the non-type-safety of `doReturn` is masking the fact that `String` is not the right type to return. – László van den Hoek Aug 19 '18 at 20:51
  • 1
    @LászlóvandenHoek compile-time safety is not tradeable to consequences of invocation of the real method if the logic requires total isolation. the fact that String is returned is clear from exception, and why the program behaved in unexpected ways using thenReturn is much more harder to find out. using of when-thenReturn is a special case for integration test cases: https://stackoverflow.com/a/29394497/7884542 – nyarian Aug 19 '18 at 21:05

2 Answers2

1

I solved the problem this way:

@Test
public void WhenTrovaImpegnoThenInvokeMongoCollectionFindOne() throws NullPointerException{
 String data = "01-11-2018";
 FindOne findResult = mock(FindOne.class);
doReturn(mongoCollection).when(collection).getMongoCollection();
doReturn(findResult).when(mongoCollection).findOne("{data:#}", data);
collection.trovaImpegno(data);
verify(mongoCollection).findOne("{data:#}", data);
}

Thank you all for your help!

asso
  • 17
  • 5
0

Seems that variable 'impegno' which is used to stub mongoCollection's find method has String type when it should represent Find type. Error description is quite clear about it.

If I understand this test case correctly, than it should look like this:

@Test
public void WhenTrovaImpegnoThenInvokeMongoCollectionFindOne(){
    String data = "01-11-2018";
    Find findResult = mock(Find.class);
    doReturn(mongoCollection).when(collection).getMongoCollection();
    doReturn(findResult).when(mongoCollection).find(eq("{data:#}"), eq(data));
    doReturn(impegno).when(findResult).as(eq(Impegno.class));
    collection.trovaImpegno(data);
    verify(mongoCollection).findOne(eq("{data:#}"), eq(data));
    verify(findResult).as(eq(Impegno.class));
}
nyarian
  • 4,085
  • 1
  • 19
  • 51
  • actually wait a minute. I see not single stub in this line: doReturn(impegno).when(mongoCollection).find("{data:#}", data).as(Impegno.class); so what is described here: when mongoCollection will return something with it's find method, than stub this result with impegno variable when I'll call as method (i honestly didn't stub result that will be returned from intermediate expression in this way but seems that it will work so). – nyarian Aug 19 '18 at 20:30
  • How can I stub with a Find type? – asso Aug 19 '18 at 20:31
  • If i don't stub I get a NullPointerException from the `imp = getMongoCollection().findOne("{data:#}", data).as(Impegno.class);` – asso Aug 19 '18 at 20:41
  • try to stub both mongocollection when find method is invoked and result of this find's method invocation which should be a mock too when as(*.class) is invoked – nyarian Aug 19 '18 at 20:43
  • can you write an example? – asso Aug 19 '18 at 20:46
  • I've attached it as a code snipped in my answer (not comment) – nyarian Aug 19 '18 at 20:46
  • You don't need those calls to `eq`. If a stubbing or verification contains no argument matchers, then the `eq` behaviour is the default. – Dawood ibn Kareem Aug 19 '18 at 20:58