Mockito UnfinishedStubbingException in tests
Ever wonder why Mockito will occasionally give you an UnfinishedStubbingException
even though you clearly finished the stubbing?
For example, the following code will fail at runtime (mockChannel
has been setup earlier):
1 2 |
|
With an exception like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This is a very good exception message. It gives you some common examples of what you might have done wrong, and how to fix them. However, our code doesn’t match any of the examples.
If we look at the generated Java for this (Tools > Kotlin > Show Kotlin Bytecode > Decompile
), it still looks like it should work - we definitely finish the stubbing:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
So why do we get an UnfinishedStubbingException
? Because we’re accessing a mock before we finish the stubbing.
The failure happens on this line from the decompiled Kotlin bytecode:
1
|
|
Let’s make this a little clearer by using descriptive names:
1
|
|
You can break this apart a little further:
1 2 |
|
So we have a reference to an OngoingStubbing
. Internally, Mockito statically maintains a reference to a MockingProgress
. When we access the mock (mockChannel.id()
) after we’ve already started the stubbing, Mockito goes off to the MockingProgress
and calls through to a validateState()
method, which does this:
1 2 3 4 5 6 7 8 9 10 |
|
Because a stubbing is still in progress, we get an UnfinishedStubbingException
. 💥
We can fix this by pulling the variable out of the mock on the line before we start the stubbing:
1 2 |
|
Of course, make sure to comment why you’re doing this for the next developer who comes along and tries to simplify this by inlining the variable.
Now that we see why it’s happening, it makes sense. Accessing a mock while setting up a different one is a bit of a code smell - they should probably be accessing some constant value. One of the best ways to prevent this situation is to use fakes not mocks for your models. Obviously, this isn’t always easy though - you might have legacy concerns that prevent this or you might need to make sure that you return the same thing from two different interfaces.
This can be a very confusing error to debug, especially when it’s deep in some utility or setup method (where it might not even be obvious that you’re accessing something on a mock), or when you’re in the process of converting some legacy code. Next time you encounter it, consider whether you can do some refactoring to make it harder to hit.
You can find the source code for this post here.