Skip to content

Commit c1122da

Browse files
authored
Add interface to verify execution order of suspending functions (#464)
1 parent 54614a8 commit c1122da

File tree

4 files changed

+256
-10
lines changed

4 files changed

+256
-10
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2018 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
package org.mockito.kotlin
27+
28+
import org.mockito.InOrder
29+
import org.mockito.verification.VerificationMode
30+
31+
interface KInOrder: InOrder {
32+
/**
33+
* Verifies certain suspending behavior <b>happened once</b> in order.
34+
*
35+
* Warning: Only one method call can be verified in the function.
36+
* Subsequent method calls are ignored!
37+
*/
38+
fun <T> verifyBlocking(mock: T, f: suspend T.() -> Unit)
39+
40+
/**
41+
* Verifies certain suspending behavior happened at least once / exact number of times / never in order.
42+
*
43+
* Warning: Only one method call can be verified in the function.
44+
* Subsequent method calls are ignored!
45+
*/
46+
fun <T> verifyBlocking(mock: T, mode: VerificationMode, f: suspend T.() -> Unit)
47+
}

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Verification.kt

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.mockito.kotlin.internal.createInstance
2929
import kotlinx.coroutines.runBlocking
3030
import org.mockito.InOrder
3131
import org.mockito.Mockito
32+
import org.mockito.kotlin.internal.KInOrderDecorator
3233
import org.mockito.verification.VerificationAfterDelay
3334
import org.mockito.verification.VerificationMode
3435
import org.mockito.verification.VerificationWithTimeout
@@ -188,42 +189,67 @@ fun ignoreStubs(vararg mocks: Any): Array<out Any> {
188189
}
189190

190191
/**
191-
* Creates [InOrder] object that allows verifying mocks in order.
192+
* Creates [KInOrder] object that allows verifying mocks in order.
192193
*
193-
* Alias for [Mockito.inOrder].
194+
* Wrapper for [Mockito.inOrder] that also allows to verify suspending method calls.
194195
*/
195-
fun inOrder(vararg mocks: Any): InOrder {
196-
return Mockito.inOrder(*mocks)!!
196+
fun inOrder(vararg mocks: Any): KInOrder {
197+
return KInOrderDecorator(Mockito.inOrder(*mocks)!!)
197198
}
198199

199200
/**
200-
* Creates [InOrder] object that allows verifying mocks in order.
201+
* Creates [KInOrder] object that allows verifying mocks in order.
201202
* Accepts a lambda to allow easy evaluation.
202203
*
203-
* Alias for [Mockito.inOrder].
204+
* Wrapper for [Mockito.inOrder] that also allows to verify suspending method calls.
204205
*/
205206
inline fun inOrder(
206207
vararg mocks: Any,
207-
evaluation: InOrder.() -> Unit
208+
evaluation: KInOrder.() -> Unit
208209
) {
209-
Mockito.inOrder(*mocks).evaluation()
210+
KInOrderDecorator(Mockito.inOrder(*mocks)).evaluation()
210211
}
211212

212213
/**
213-
* Allows [InOrder] verification for a single mocked instance:
214+
* Allows [KInOrder] verification for a single mocked instance:
214215
*
215216
* mock.inOrder {
216217
* verify().foo()
218+
* verifyBlocking { bar() }
217219
* }
218220
*
219221
*/
220222
inline fun <T> T.inOrder(block: InOrderOnType<T>.() -> Any) {
221223
block.invoke(InOrderOnType(this))
222224
}
223225

224-
class InOrderOnType<T>(private val t: T) : InOrder by inOrder(t as Any) {
226+
class InOrderOnType<T>(private val t: T) : KInOrder by inOrder(t as Any) {
225227

228+
/**
229+
* Verifies certain behavior <b>happened once</b> in order.
230+
*/
226231
fun verify(): T = verify(t)
232+
233+
/**
234+
* Verifies certain behavior happened at least once / exact number of times / never in order.
235+
*/
236+
fun verify(mode: VerificationMode): T = verify(t, mode)
237+
238+
/**
239+
* Verifies certain suspending behavior <b>happened once</b> in order.
240+
*
241+
* Warning: Only one method call can be verified in the function.
242+
* Subsequent method calls are ignored!
243+
*/
244+
fun verifyBlocking(f: suspend T.() -> Unit) = verifyBlocking(t, f)
245+
246+
/**
247+
* Verifies certain suspending behavior happened at least once / exact number of times / never in order.
248+
*
249+
* Warning: Only one method call can be verified in the function.
250+
* Subsequent method calls are ignored!
251+
*/
252+
fun verifyBlocking(mode: VerificationMode, f: suspend T.() -> Unit) = verifyBlocking(t, mode, f)
227253
}
228254

229255
/**
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2018 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
package org.mockito.kotlin.internal
27+
28+
import kotlinx.coroutines.runBlocking
29+
import org.mockito.InOrder
30+
import org.mockito.kotlin.KInOrder
31+
import org.mockito.verification.VerificationMode
32+
33+
class KInOrderDecorator(private val inOrder: InOrder) : KInOrder, InOrder by inOrder {
34+
override fun <T> verifyBlocking(mock: T, f: suspend T.() -> Unit) {
35+
val m = verify(mock)
36+
runBlocking { m.f() }
37+
}
38+
39+
override fun <T> verifyBlocking(mock: T, mode: VerificationMode, f: suspend T.() -> Unit) {
40+
val m = verify(mock, mode)
41+
runBlocking { m.f() }
42+
}
43+
}

mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import kotlinx.coroutines.withContext
1010
import kotlinx.coroutines.*
1111
import kotlinx.coroutines.channels.actor
1212
import org.junit.Assert.assertEquals
13+
import org.junit.Assert.assertThrows
1314
import org.junit.Test
15+
import org.mockito.InOrder
1416
import org.mockito.kotlin.*
1517
import java.util.*
1618

@@ -255,6 +257,134 @@ class CoroutinesTest {
255257
assertEquals(5, asyncTask.await())
256258
}
257259
}
260+
261+
@Test
262+
fun inOrderRemainsCompatible() {
263+
/* Given */
264+
val fixture: SomeInterface = mock()
265+
266+
/* When */
267+
val inOrder = inOrder(fixture)
268+
269+
/* Then */
270+
expect(inOrder).toBeInstanceOf<InOrder>()
271+
}
272+
273+
@Test
274+
fun inOrderSuspendingCalls() {
275+
/* Given */
276+
val fixtureOne: SomeInterface = mock()
277+
val fixtureTwo: SomeInterface = mock()
278+
279+
/* When */
280+
runBlocking {
281+
fixtureOne.suspending()
282+
fixtureTwo.suspending()
283+
}
284+
285+
/* Then */
286+
val inOrder = inOrder(fixtureOne, fixtureTwo)
287+
inOrder.verifyBlocking(fixtureOne) { suspending() }
288+
inOrder.verifyBlocking(fixtureTwo) { suspending() }
289+
}
290+
291+
@Test
292+
fun inOrderSuspendingCallsFailure() {
293+
/* Given */
294+
val fixtureOne: SomeInterface = mock()
295+
val fixtureTwo: SomeInterface = mock()
296+
297+
/* When */
298+
runBlocking {
299+
fixtureOne.suspending()
300+
fixtureTwo.suspending()
301+
}
302+
303+
/* Then */
304+
val inOrder = inOrder(fixtureOne, fixtureTwo)
305+
inOrder.verifyBlocking(fixtureTwo) { suspending() }
306+
assertThrows(AssertionError::class.java) {
307+
inOrder.verifyBlocking(fixtureOne) { suspending() }
308+
}
309+
}
310+
311+
@Test
312+
fun inOrderBlockSuspendingCalls() {
313+
/* Given */
314+
val fixtureOne: SomeInterface = mock()
315+
val fixtureTwo: SomeInterface = mock()
316+
317+
/* When */
318+
runBlocking {
319+
fixtureOne.suspending()
320+
fixtureTwo.suspending()
321+
}
322+
323+
/* Then */
324+
inOrder(fixtureOne, fixtureTwo) {
325+
verifyBlocking(fixtureOne) { suspending() }
326+
verifyBlocking(fixtureTwo) { suspending() }
327+
}
328+
}
329+
330+
@Test
331+
fun inOrderBlockSuspendingCallsFailure() {
332+
/* Given */
333+
val fixtureOne: SomeInterface = mock()
334+
val fixtureTwo: SomeInterface = mock()
335+
336+
/* When */
337+
runBlocking {
338+
fixtureOne.suspending()
339+
fixtureTwo.suspending()
340+
}
341+
342+
/* Then */
343+
inOrder(fixtureOne, fixtureTwo) {
344+
verifyBlocking(fixtureTwo) { suspending() }
345+
assertThrows(AssertionError::class.java) {
346+
verifyBlocking(fixtureOne) { suspending() }
347+
}
348+
}
349+
}
350+
351+
@Test
352+
fun inOrderOnObjectSuspendingCalls() {
353+
/* Given */
354+
val fixture: SomeInterface = mock()
355+
356+
/* When */
357+
runBlocking {
358+
fixture.suspendingWithArg(1)
359+
fixture.suspendingWithArg(2)
360+
}
361+
362+
/* Then */
363+
fixture.inOrder {
364+
verifyBlocking { suspendingWithArg(1) }
365+
verifyBlocking { suspendingWithArg(2) }
366+
}
367+
}
368+
369+
@Test
370+
fun inOrderOnObjectSuspendingCallsFailure() {
371+
/* Given */
372+
val fixture: SomeInterface = mock()
373+
374+
/* When */
375+
runBlocking {
376+
fixture.suspendingWithArg(1)
377+
fixture.suspendingWithArg(2)
378+
}
379+
380+
/* Then */
381+
fixture.inOrder {
382+
verifyBlocking { suspendingWithArg(2) }
383+
assertThrows(AssertionError::class.java) {
384+
verifyBlocking { suspendingWithArg(1) }
385+
}
386+
}
387+
}
258388
}
259389

260390
interface SomeInterface {

0 commit comments

Comments
 (0)