Revamp of ongoing stubbing for suspend functions #552
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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:
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:
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.