Skip to content

Conversation

@m-koops
Copy link
Contributor

@m-koops m-koops commented Nov 21, 2025

This PR provides a fix for #458 and will prevent future confusion of users around the stubbing of suspend functions.
This PR also does the groundwork for #492 (support for unsigned integers), as the unsigned integers are a flavor of value classes within Kotlin.

This PR superseeds PR #552, as the change in this new PR serves more user scenarios (both ongoing and reverse stubbing) with less complexity. Therefore, I will close #552.

===

In various attemps mocking support for coroutines was introduced in Mockito-kotlin.

Problem with the current implementation is that the user should be keen to use the correct answer method (the ones that apply SuspendableAnswer) to have Mockito work properly with suspend function. Incidentally using the non-suspendable flavour of the answer methods in Mockito-kotlin can lead to confusing error, e.g. null values and cast exceptions.

To get suspend functions with a value class return value stubbed properly, one needs to apply SuspendableAnswer and include a rather 'magic' delay(1) within the answer suspend lambda to get value class instances properly boxed when they are returned from the mock by a stubbed function call.
I was hinted of all this by the comment of @Lingviston (#456 (comment)). He explains that the value class answer is properly boxed when you take a specific approach:

doSuspendableReturn {
    delay(1)
    value
}

It is really nice that this particular solution has been found and shared in the above metioned ticket, but users of Mockito-kotlin need to be aware of this rather magic implementation quirk to get stubbing of suspend functions work properly with value classes.

This PR takes stubbing support for coroutines to a next level. In the Mockito-kotlin stubbing functions the Mockito answers are all wrapped in the new CoroutineAwareAnswer. When the answer method of this CoroutineAwareAnswer is called by Mockito, then this wrapping answer class will first determine whether the answer is being tied to a suspend function call. If so, the CoroutineAwareAnswer will take care of the following:

  • it will wrap the original answer in a SuspendableAnswer (an existing class) to wire Mockito properly to the call back structure of suspend functions
  • it will apply the recipe with delay(1) magic to enforce suspension (leading to proper boxing of value class types)
  • last, it will box results yielding from real method calls in the mock to a value class, if needed so

In case the CoroutineAwareAnswer is being tied to a synchronous method/function call it will just forward its invocation to the nested delegate answer (the original Mockito answer).

As long as the user is applying the Mockito-kotlin stubbing functions, this library should now take care of dealing with all intricacies related to suspend functions and value classes. For example, a user can now specify the value class return value of a stubbed suspend function call by simply applying doReturn(valueClass).

The stubbing functions that already existed, are:

  • doReturn(t: T)
  • doReturn(t: T, vararg ts: T)
  • doReturnConsecutively(ts: List<T>)
  • doThrow(t: Throwable)
  • doThrow( t: Throwable, vararg ts: Throwable)
  • doThrow(t: KClass<out Throwable>)
  • doThrow(t: KClass<out Throwable>, vararg ts: KClass<out Throwable>)
  • doAnswer(answer: Answer<*>)
  • doAnswer(answer: (KInvocationOnMock) -> T?)
  • doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?)

New ones added to OngoingStubbing, are:

  • thenReturn(t: T)
  • doReturnConsecutively(vararg ts: T)
  • doCallRealMethod()
  • callRealMethod()

All these stubbing functions wrap answers in a CoroutineAwareAnswer.

The suite of unit tests have been extended with a load of new tests to cover the many scenarios to stub a function call, both suspending and non-suspending. In the Classes file, I have renamed the Methods test interface to SynchronousFunctions and I have added a new interface SuspendFunctions which combines all suspend functions used in the various tests. I have updated existing tests to let them use these 2 interfaces as much as possible. Also I have made a start to use the test naming strategy with back ticks strings starting with "should" , like `should stub function call`(). This way we should end up with better readable test names.

Follow ups:

  • I have taken a look in possibilities to collapse whenever/wheneverBlocking and on/onBlocking by introducing overloads that accept either synchronous or supend function calls. Taking this step would make the API even more clean. But it turns out that the Kotlin compiler can not deal well with the overload resolution. Improvements have just been made in Kotlin compiler 2.2 and these improvements will become general available in 2.3 (see https://kotlinlang.org/docs/whatsnew2220.html#improved-overload-resolution-for-lambdas-with-suspend-function-types). As long as Mockito-kotlin supports Kotlin versions < 2.3, explicit distinct function names will still be required.

@m-koops m-koops force-pushed the coroutine-aware-answer branch from 8149507 to 7c0683a Compare November 21, 2025 20:07
@TimvdLippe
Copy link
Contributor

This is a massive PR and looks like a significant overhaul of the current implementation. I don't doubt that this is an improvement, but reviewing this is going to take a while. I am not sure if and when we will have time for it, nor the complete context. If you have others that are knowledgeable in this area that can help out with reviewing, that would be appreciated.

@m-koops
Copy link
Contributor Author

m-koops commented Nov 27, 2025

Thank you for your first feedback. I agree that it has become a rather big change. But important to mention, the change is 100% backward compatible on the API that Mockito-kotlin was already offering.
I will try to involve some knowledgeable Kotlin devevlopers for reviewing.

And on the other hand, I can also offer to break down the big PR into a train of smaller PRs. To see the implementation evolve in multiple steps would probably provide more guidance in the review process.
Would that help for the process?

@TimvdLippe
Copy link
Contributor

For sure, let's do both. First, get some other Kotlin developers for feedback on the overall design. Once we have rough concensus that this is worth doing, we should break it up into smaller chunks so it is understandable what each part is doing.

Thanks again for the work, it's just a bit overwhelming and difficult to grasp for somebody who doesn't write Kotlin on a daily basis.

@m-koops
Copy link
Contributor Author

m-koops commented Nov 29, 2025

As discussed, I have prepared PR #554, as a first subset of this massive PR. It only touches on improving test coverage of the varous facets of the current implementation of Mockito-kotlin. No mainline changes are involved yet in this PR.

Please ignore this PR for now and instead let's prcoeed with multiple smaller PRs that are more manageables and comprehensible. Eventualy they will add up to a change comparable with this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants