56

I have encountered what I assume might be a bug with Mockito, but was wondering if anyone else can shed light as to why this test doesn't work.

Basically, I have two objects, like this:

public class FirstObject {
    private SecondObject secondObject;
    public SecondObject getSecondObject() { return secondObject; }
}

public class SecondObject {
    private String name;
    public String getName() { return name; }
}

The first object is mocked via annotation and the before method:

@Mock
FirstObject mockedFirstObject;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
}

The second object is mocked in a method:

public SecondObject setupMockedSecondObject() {
    SecondObject secondObject = Mockito.mock(SecondObject.class);
    Mockito.when(secondObject.getName()).thenReturn("MockObject");
    return secondObject;
}

When thenReturn contains a direct call to this method to setup and obtain a mock of the second object, it fails:

@Test
public void notWorkingTest() {
    Mockito.when(mockedFirstObject.getSecondObject()).thenReturn(setupMockedSecondObject());
    Assert.assertEquals(mockedFirstObject.getSecondObject().getName(), "MockObject");
}

But, when the mock returned by the same method is assigned to a local variable, which is used in thenReturn, it works:

@Test
public void workingTest() {
    SecondObject mockedSecondObject = setupMockedSecondObject();
    Mockito.when(mockedFirstObject.getSecondObject()).thenReturn(mockedSecondObject);
    Assert.assertEquals(mockedFirstObject.getSecondObject().getName(), "MockObject");
}

Are we doing something wrong or is this indeed a bug/limitation in Mockito? Is there a deliberate reason for this not working?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
LawrenceWeetman
  • 613
  • 1
  • 6
  • 8
  • This is likely due to how Mockito implements its `when`/`thenReturn` (or other) calls. You're breaking that chain by invoking another `when`/`thenReturn` cycle before calling the previous `thenReturn`. – Sotirios Delimanolis Nov 30 '15 at 18:16

3 Answers3

78

This is indeed a limitation of Mockito, and it is referenced in their FAQ:

Can I thenReturn() an inlined mock()?

Unfortunately you cannot do this:

when(m.foo()).thenReturn(mock(Foo.class));
//                         ^

The reason is that detecting unfinished stubbing wouldn't work if we allow above construct. We consider is as a 'trade off' of framework validation (see also previous FAQ entry). However you can slightly change the code to make it working:

//extract local variable and start smiling:
Foo foo = mock(Foo.class);
when(m.foo()).thenReturn(foo);

The workaround, as mentioned, is to store the desired returned value in a local variable, like you have done.

The way I understand it is that Mockito validates the usage you make of it every time you call its methods. When another method is called during an on-going stubbing process, you are breaking its validation process.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Now you can! This is an old answer. – Hamzeh Soboh Mar 13 '21 at 23:03
  • @HamzehSoboh No you can't because this method is run before, and we want it executed after as required by "then return". This is a limitation because states can't be updated by spied foo() method before thenReturn is executed. The thenReturn parameter can't benefit from the state set by the spied or mocked method. – user1767316 Mar 22 '21 at 14:14
4

You can't use a method in thenReturn, but you can in thenAnswer Your code will be called after the when condition will occur, unlike any workaround based on thenReturn

Thus you could write:

@Test
public void nowWorkingTest() {
    Mockito.when(mockedFirstObject.getSecondObject()).thenAnswer(new Answer<Map>() {
        @Override
        public Map answer(InvocationOnMock invocation) {
            return setupMockedSecondObject();
        }
    });
    Assert.assertEquals(mockedFirstObject.getSecondObject().getName(), "MockObject");
}

Let find another example here

user1767316
  • 3,276
  • 3
  • 37
  • 46
  • Yes as @user1767316 suggested, have to use thenAnswer for mocked object method to return another mocked object. In Kotlin, you can do similar as in one-liner as: ` `when`(mockedFirstObject.getSecondObject).thenAnswer { mock { it.name = "MockObject" } } ` – bh4r4th Jan 10 '23 at 01:23
  • Shorter via lambda: replace `.thenReturn(returnValue)` with `.thenAnswer(i -> returnValue)`. – wilmol May 16 '23 at 09:48
-3
@Test
public void testAuthenticate_ValidCredentials() throws FailedToAuthenticateException {

    String username = "User1";
    String password = "Password";
    /*Configure Returning True with when...thenReturn configuration on mock Object - Q5*/
   //Write your code here
    assertTrue(authenticator.authenticateUser(username, password));
}
mnestorov
  • 4,116
  • 2
  • 14
  • 24
Gaurav
  • 1
  • 1