Skip to content

Conversation

@m-koops
Copy link
Contributor

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

Provides a fix for #458 and many future confusions.

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

Important part of the puzzle is to apply SuspendableAnswer for stubbing your mocked suspend function, and to include a '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
}

Really nice that this solution was found and shared, but users of Mockito-kotlin need to know this rather magic implementation quirk to get mocking of coroutines work properly with value classes.

This PR takes stub support for coroutines to a next level, for ongoing stubbing at least.

By introducing CoroutinesOngoingStubbing next to OngoingStubbing, the user can be offered consistent API for stubbing either synchronous or suspend functions. Both implementations offer now:

  • doReturn(t: T)
  • thenReturn(t: T)
  • doReturn(t: T, vararg ts: T)
  • doReturnConsecutively(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?)

Additionally the whenever/wheneverBlocking/on/onBlocking stubbers are now embellished with logic to detect that suspend functions are being stubbed with a synchronous stubber and visa versa. The stubbers will log a warning with the Mockito logger when stubbers are misused for synchronous or susspend function calls.

The existing API on(...).doSuspendableAnswer { ... } is kept for now to be backwards compatible, but this rather immature API is marked deprecated and can be dropped in the future in favor of the new consistent API.

The suite of unit tests have been extended with a load of new tests to cover the many scenarios to stub a function call, existing tests have been updated to let them use the central interfaces SynchronousFunctions and SuspendFunctions as much as possible. Also I have made a start to use the test naming strategy with "should" and back ticks, like `should ....` to end up with better readable test names.

Follow ups:

  • The reverse stubbing of coroutines (e.g. doSuspendableAnswer { .... } whenever(mock).doSometing()) is not yet updated. The main problem here is, that the suspend function is being called outside the scope of the stubber functions. It is only within Mockito Core that the suspend function call is being recorded and being stubbed with the already prepared answer(s). Reworking this reverse stubbing to enhance coroutine support behind the scenes will take another leap.
  • 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 be required.

…utinesOngoingStubbing as return value of the wheneverBlocking/onBlocking functions. Introducing this coroutines specific implementation of OngoingStubbing allowed for clean and similar API to stub either synchronous or suspend functions. The 'magic' implementation details of applying SuspendableAnswer for suspend functions and to delay within the answer suspend lambda to enforce proper boxing of value classes is all being hidden away from the ordinary users.
@m-koops
Copy link
Contributor Author

m-koops commented Nov 18, 2025

Hold this PR for now. After giving it another thought, I guess I can up with an even cleaner solution.

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.

1 participant