0

Can I pass mocked object as an argument to thenThrow() method? I have something like this:

public class MyException extends Exception {
    public MyException(MockedClass mockedClass) {
        super("My message:" + mockedClass.doSth("foo"));
    }
}

public class TestedServiceTest {
    @Mock
    MockedClass mockedClass;

    @Mock
    AnotherClass anotherClass;

    @Before
    public void init() {
        when(mockedClass.doSth(anyString())).thenAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                return invocation.getArgument(0);
            }

        });
    }

    @Test
    public void notWorkingTestMethod() {
        when(anotherClass.doSomething()).thenThrow(new MyException(mockedClass));
    }

notWorkingTestMethod() throws org.mockito.exceptions.misusing.UnfinishedStubbingException However if I use the same technique on void method it doesn't complain anymore:

    @Test
    public void workingTestMethod() {
        doThrow(new MyException(mockedClass)).when(anotherClass).doSomethingVoid();
    }
}

Is there any other possible reason it doesn't work?

1 Answers1

1

To understand why this is happening you need to understand a bit about how Mockito works.

Mockito uses internal static state to keep track of what setup is being done to which mocks. This does allow for clear and expressive mocking, but sometimes it does cause a violation of the Principle of Least Astonishment, as it seems you've encountered here.

Let's consider the line in your not-working test method:

when(anotherClass.doSomething()).thenThrow(new MyException(mockedClass));

Mockito sees these interactions, in the following order:

  1. a call to anotherClass.doSomething(), which Mockito will record internally as the last invocation on a mock, because this mock method might be about to be set up to do something.
  2. a call to the static when method, so Mockito knows that the behaviour of anotherClass.doSomething() is being set up.
  3. a call to mockedClass.doSth() in the MyException constructor. This is another invocation on a mock, which Mockito wasn't expecting.

At this point, the doThrow() method hasn't been called, so Mockito can't know that you will later call it to set up the exception to throw. Instead, it looks to Mockito as if you are writing:

when(anotherClass.doSomething());
when(mockedClass.doSth()).then....

Hence the exception about unfinished stubbing.

The fix, as suggested by @marcellorvalle in the comment, is to move the exception out into a local variable:

MyException myException = new MyException(mockedClass);
when(anotherClass.doSomething()).thenThrow(myException);

In most cases, extracting a local variable like this won't change the behaviour of the code. But it does change the order of the three interactions with Mockito I listed above. It is now:

  1. a call to mockedClass.doSth() in the constructor of your exception, which Mockito will record internally as the last invocation on a mock.
  2. a call to anotherClass.doSomething(), which Mockito will record internally as the last invocation on a mock, replacing the previous one.
  3. a call to the static when method, so Mockito knows that the behaviour of anotherClass.doSomething() is being set up.

The next interaction with Mockito is then the call to thenThrow(), which Mockito can then link to the call to anotherClass.doSomething().

As for your workingTestMethod() method, it has the line

doThrow(new MyException(mockedClass)).when(anotherClass).doSomethingVoid();

This mock setup works, because this time, the order of interactions with Mockito is:

  1. a call to mockedClass.doSth() in the constructor of your exception, which Mockito will record internally as the last invocation on a mock. (It happens that in this case, this last-invocation isn't used.)
  2. a call to the static doThrow() method. At this point, Mockito doesn't know what mock or what method to throw the exception for, so it can only make a note of the exception.
  3. a call to the when method on the Stubber instance that doThrow() returns. This tells Mockito which mock is being set up, and also to watch out for whatever the next invocation of a mock method is, as that is what is being set up. It looks like this when method returns the mock that it is given.
  4. a call to the doSomethingVoid() method of your mock. Mockito can then link the exception that was to be thrown to this method.
Luke Woodward
  • 63,336
  • 16
  • 89
  • 104