Skip to content

Commit

Permalink
Add interface to verify execution order of suspending functions (#464)
Browse files Browse the repository at this point in the history
  • Loading branch information
seyfahni committed Oct 25, 2022
1 parent 54614a8 commit c1122da
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 10 deletions.
47 changes: 47 additions & 0 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInOrder.kt
@@ -0,0 +1,47 @@
/*
* The MIT License
*
* Copyright (c) 2018 Niek Haarman
* Copyright (c) 2007 Mockito contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.mockito.kotlin

import org.mockito.InOrder
import org.mockito.verification.VerificationMode

interface KInOrder: InOrder {
/**
* Verifies certain suspending behavior <b>happened once</b> in order.
*
* Warning: Only one method call can be verified in the function.
* Subsequent method calls are ignored!
*/
fun <T> verifyBlocking(mock: T, f: suspend T.() -> Unit)

/**
* Verifies certain suspending behavior happened at least once / exact number of times / never in order.
*
* Warning: Only one method call can be verified in the function.
* Subsequent method calls are ignored!
*/
fun <T> verifyBlocking(mock: T, mode: VerificationMode, f: suspend T.() -> Unit)
}
46 changes: 36 additions & 10 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Verification.kt
Expand Up @@ -29,6 +29,7 @@ import org.mockito.kotlin.internal.createInstance
import kotlinx.coroutines.runBlocking
import org.mockito.InOrder
import org.mockito.Mockito
import org.mockito.kotlin.internal.KInOrderDecorator
import org.mockito.verification.VerificationAfterDelay
import org.mockito.verification.VerificationMode
import org.mockito.verification.VerificationWithTimeout
Expand Down Expand Up @@ -188,42 +189,67 @@ fun ignoreStubs(vararg mocks: Any): Array<out Any> {
}

/**
* Creates [InOrder] object that allows verifying mocks in order.
* Creates [KInOrder] object that allows verifying mocks in order.
*
* Alias for [Mockito.inOrder].
* Wrapper for [Mockito.inOrder] that also allows to verify suspending method calls.
*/
fun inOrder(vararg mocks: Any): InOrder {
return Mockito.inOrder(*mocks)!!
fun inOrder(vararg mocks: Any): KInOrder {
return KInOrderDecorator(Mockito.inOrder(*mocks)!!)
}

/**
* Creates [InOrder] object that allows verifying mocks in order.
* Creates [KInOrder] object that allows verifying mocks in order.
* Accepts a lambda to allow easy evaluation.
*
* Alias for [Mockito.inOrder].
* Wrapper for [Mockito.inOrder] that also allows to verify suspending method calls.
*/
inline fun inOrder(
vararg mocks: Any,
evaluation: InOrder.() -> Unit
evaluation: KInOrder.() -> Unit
) {
Mockito.inOrder(*mocks).evaluation()
KInOrderDecorator(Mockito.inOrder(*mocks)).evaluation()
}

/**
* Allows [InOrder] verification for a single mocked instance:
* Allows [KInOrder] verification for a single mocked instance:
*
* mock.inOrder {
* verify().foo()
* verifyBlocking { bar() }
* }
*
*/
inline fun <T> T.inOrder(block: InOrderOnType<T>.() -> Any) {
block.invoke(InOrderOnType(this))
}

class InOrderOnType<T>(private val t: T) : InOrder by inOrder(t as Any) {
class InOrderOnType<T>(private val t: T) : KInOrder by inOrder(t as Any) {

/**
* Verifies certain behavior <b>happened once</b> in order.
*/
fun verify(): T = verify(t)

/**
* Verifies certain behavior happened at least once / exact number of times / never in order.
*/
fun verify(mode: VerificationMode): T = verify(t, mode)

/**
* Verifies certain suspending behavior <b>happened once</b> in order.
*
* Warning: Only one method call can be verified in the function.
* Subsequent method calls are ignored!
*/
fun verifyBlocking(f: suspend T.() -> Unit) = verifyBlocking(t, f)

/**
* Verifies certain suspending behavior happened at least once / exact number of times / never in order.
*
* Warning: Only one method call can be verified in the function.
* Subsequent method calls are ignored!
*/
fun verifyBlocking(mode: VerificationMode, f: suspend T.() -> Unit) = verifyBlocking(t, mode, f)
}

/**
Expand Down
@@ -0,0 +1,43 @@
/*
* The MIT License
*
* Copyright (c) 2018 Niek Haarman
* Copyright (c) 2007 Mockito contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.mockito.kotlin.internal

import kotlinx.coroutines.runBlocking
import org.mockito.InOrder
import org.mockito.kotlin.KInOrder
import org.mockito.verification.VerificationMode

class KInOrderDecorator(private val inOrder: InOrder) : KInOrder, InOrder by inOrder {
override fun <T> verifyBlocking(mock: T, f: suspend T.() -> Unit) {
val m = verify(mock)
runBlocking { m.f() }
}

override fun <T> verifyBlocking(mock: T, mode: VerificationMode, f: suspend T.() -> Unit) {
val m = verify(mock, mode)
runBlocking { m.f() }
}
}
130 changes: 130 additions & 0 deletions mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt
Expand Up @@ -10,7 +10,9 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.actor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test
import org.mockito.InOrder
import org.mockito.kotlin.*
import java.util.*

Expand Down Expand Up @@ -255,6 +257,134 @@ class CoroutinesTest {
assertEquals(5, asyncTask.await())
}
}

@Test
fun inOrderRemainsCompatible() {
/* Given */
val fixture: SomeInterface = mock()

/* When */
val inOrder = inOrder(fixture)

/* Then */
expect(inOrder).toBeInstanceOf<InOrder>()
}

@Test
fun inOrderSuspendingCalls() {
/* Given */
val fixtureOne: SomeInterface = mock()
val fixtureTwo: SomeInterface = mock()

/* When */
runBlocking {
fixtureOne.suspending()
fixtureTwo.suspending()
}

/* Then */
val inOrder = inOrder(fixtureOne, fixtureTwo)
inOrder.verifyBlocking(fixtureOne) { suspending() }
inOrder.verifyBlocking(fixtureTwo) { suspending() }
}

@Test
fun inOrderSuspendingCallsFailure() {
/* Given */
val fixtureOne: SomeInterface = mock()
val fixtureTwo: SomeInterface = mock()

/* When */
runBlocking {
fixtureOne.suspending()
fixtureTwo.suspending()
}

/* Then */
val inOrder = inOrder(fixtureOne, fixtureTwo)
inOrder.verifyBlocking(fixtureTwo) { suspending() }
assertThrows(AssertionError::class.java) {
inOrder.verifyBlocking(fixtureOne) { suspending() }
}
}

@Test
fun inOrderBlockSuspendingCalls() {
/* Given */
val fixtureOne: SomeInterface = mock()
val fixtureTwo: SomeInterface = mock()

/* When */
runBlocking {
fixtureOne.suspending()
fixtureTwo.suspending()
}

/* Then */
inOrder(fixtureOne, fixtureTwo) {
verifyBlocking(fixtureOne) { suspending() }
verifyBlocking(fixtureTwo) { suspending() }
}
}

@Test
fun inOrderBlockSuspendingCallsFailure() {
/* Given */
val fixtureOne: SomeInterface = mock()
val fixtureTwo: SomeInterface = mock()

/* When */
runBlocking {
fixtureOne.suspending()
fixtureTwo.suspending()
}

/* Then */
inOrder(fixtureOne, fixtureTwo) {
verifyBlocking(fixtureTwo) { suspending() }
assertThrows(AssertionError::class.java) {
verifyBlocking(fixtureOne) { suspending() }
}
}
}

@Test
fun inOrderOnObjectSuspendingCalls() {
/* Given */
val fixture: SomeInterface = mock()

/* When */
runBlocking {
fixture.suspendingWithArg(1)
fixture.suspendingWithArg(2)
}

/* Then */
fixture.inOrder {
verifyBlocking { suspendingWithArg(1) }
verifyBlocking { suspendingWithArg(2) }
}
}

@Test
fun inOrderOnObjectSuspendingCallsFailure() {
/* Given */
val fixture: SomeInterface = mock()

/* When */
runBlocking {
fixture.suspendingWithArg(1)
fixture.suspendingWithArg(2)
}

/* Then */
fixture.inOrder {
verifyBlocking { suspendingWithArg(2) }
assertThrows(AssertionError::class.java) {
verifyBlocking { suspendingWithArg(1) }
}
}
}
}

interface SomeInterface {
Expand Down

0 comments on commit c1122da

Please sign in to comment.