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:
- 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.
- a call to the static
when
method, so Mockito knows that the behaviour of anotherClass.doSomething()
is being set up.
- 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:
- a call to
mockedClass.doSth()
in the constructor of your exception, which Mockito will record internally as the last invocation on a mock.
- a call to
anotherClass.doSomething()
, which Mockito will record internally as the last invocation on a mock, replacing the previous one.
- 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:
- 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.)
- 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.
- 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.
- a call to the
doSomethingVoid()
method of your mock. Mockito can then link the exception that was to be thrown to this method.