Skip to content

Commit fe0e306

Browse files
authored
Merge pull request #1234 from T45K/introduce_verifyCount_DSL
Introduce `verifyCount` DSL
2 parents 51f65d4 + b11b732 commit fe0e306

11 files changed

Lines changed: 246 additions & 24 deletions

File tree

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,26 @@ verify(exactly = 0) { car.accelerate(fromSpeed = 30, toSpeed = 10) } // means no
674674
confirmVerified(car)
675675
```
676676

677+
Or you can use `verifyCount`:
678+
679+
```kotlin
680+
681+
val car = mockk<Car>(relaxed = true)
682+
683+
car.accelerate(fromSpeed = 10, toSpeed = 20)
684+
car.accelerate(fromSpeed = 10, toSpeed = 30)
685+
car.accelerate(fromSpeed = 20, toSpeed = 30)
686+
687+
// all pass
688+
verifyCount {
689+
(3..5) * { car.accelerate(allAny(), allAny()) } // same as verify(atLeast = 3, atMost = 5) { car.accelerate(allAny(), allAny()) }
690+
1 * { car.accelerate(fromSpeed = 10, toSpeed = 20) } // same as verify(exactly = 1) { car.accelerate(fromSpeed = 10, toSpeed = 20) }
691+
0 * { car.accelerate(fromSpeed = 30, toSpeed = 10) } // same as verify(exactly = 0) { car.accelerate(fromSpeed = 30, toSpeed = 10) }
692+
}
693+
694+
confirmVerified(car)
695+
```
696+
677697
### Verification order
678698

679699
* `verifyAll` verifies that all calls happened without checking their order.

modules/mockk-dsl/api/mockk-dsl.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,18 @@ public final class io/mockk/MockKAssertScope {
531531
public final fun getActual ()Ljava/lang/Object;
532532
}
533533

534+
public final class io/mockk/MockKCallCountCoVerificationScope {
535+
public fun <init> ()V
536+
public final fun times (ILkotlin/jvm/functions/Function2;)V
537+
public final fun times (Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function2;)V
538+
}
539+
540+
public final class io/mockk/MockKCallCountVerificationScope {
541+
public fun <init> ()V
542+
public final fun times (ILkotlin/jvm/functions/Function1;)V
543+
public final fun times (Lkotlin/ranges/IntRange;Lkotlin/jvm/functions/Function1;)V
544+
}
545+
534546
public final class io/mockk/MockKCancellationRegistry {
535547
public static final field INSTANCE Lio/mockk/MockKCancellationRegistry;
536548
public final fun cancelAll ()V

modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -690,9 +690,10 @@ open class MockKMatcherScope(
690690
val lambda: CapturingSlot<Function<*>>
691691
) {
692692

693-
fun <T: Any> match(matcher: Matcher<T>, kclass: KClass<T>): T {
693+
fun <T : Any> match(matcher: Matcher<T>, kclass: KClass<T>): T {
694694
return callRecorder.matcher(matcher, kclass)
695695
}
696+
696697
inline fun <reified T : Any> match(matcher: Matcher<T>): T {
697698
return callRecorder.matcher(matcher, T::class)
698699
}
@@ -720,6 +721,7 @@ open class MockKMatcherScope(
720721
*/
721722
inline fun <reified T : Any> eq(value: T, inverse: Boolean = false): T =
722723
match(EqMatcher(value, inverse = inverse))
724+
723725
/**
724726
* Matches if the value is not equal to the provided [value] via the `deepEquals` function.
725727
*/
@@ -730,6 +732,7 @@ open class MockKMatcherScope(
730732
*/
731733
inline fun <reified T : Any> refEq(value: T, inverse: Boolean = false): T =
732734
match(EqMatcher(value, ref = true, inverse = inverse))
735+
733736
/**
734737
* Matches if the value is not equal to the provided [value] via reference comparison.
735738
*/
@@ -738,13 +741,15 @@ open class MockKMatcherScope(
738741
/**
739742
* Matches any argument given a [KClass]
740743
*/
741-
fun <T: Any> any(classifier: KClass<T>): T = match(ConstantMatcher(true), classifier)
744+
fun <T : Any> any(classifier: KClass<T>): T = match(ConstantMatcher(true), classifier)
745+
742746
/**
743747
* Matches any argument.
744748
*/
745749
inline fun <reified T : Any> any(): T = match(ConstantMatcher(true))
746750

747751
inline fun <reified T : Any> capture(lst: MutableList<T>): T = match(CaptureMatcher(lst, T::class))
752+
748753
/**
749754
* Captures a non-nullable value to a [CapturingSlot].
750755
*
@@ -782,7 +787,8 @@ open class MockKMatcherScope(
782787
* network.download("testfile")
783788
* // slot.captured is now "testfile"
784789
*/
785-
inline fun <reified T : Any> captureNullable(lst: CapturingSlot<T?>): T? = match(CapturingNullableSlotMatcher(lst, T::class))
790+
inline fun <reified T : Any> captureNullable(lst: CapturingSlot<T?>): T? =
791+
match(CapturingNullableSlotMatcher(lst, T::class))
786792

787793
/**
788794
* Captures a nullable value to a [MutableList].
@@ -797,12 +803,14 @@ open class MockKMatcherScope(
797803
* Matches if the value is equal to the provided [value] via the `compareTo` function.
798804
*/
799805
inline fun <reified T : Comparable<T>> cmpEq(value: T): T = match(ComparingMatcher(value, 0, T::class))
806+
800807
/**
801808
* Matches if the value is more than the provided [value] via the `compareTo` function.
802809
* @param andEquals matches more than or equal to
803810
*/
804811
inline fun <reified T : Comparable<T>> more(value: T, andEquals: Boolean = false): T =
805812
match(ComparingMatcher(value, if (andEquals) 2 else 1, T::class))
813+
806814
/**
807815
* Matches if the value is less than the provided [value] via the `compareTo` function.
808816
* @param andEquals matches less than or equal to
@@ -824,10 +832,12 @@ open class MockKMatcherScope(
824832
* Combines two matchers via a logical and.
825833
*/
826834
inline fun <reified T : Any> and(left: T, right: T): T = match(AndOrMatcher<T>(true, left, right))
835+
827836
/**
828837
* Combines two matchers via a logical or.
829838
*/
830839
inline fun <reified T : Any> or(left: T, right: T): T = match(AndOrMatcher<T>(false, left, right))
840+
831841
/**
832842
* Negates the matcher.
833843
*/
@@ -840,6 +850,7 @@ open class MockKMatcherScope(
840850
*/
841851
inline fun <reified T : Any> isNull(inverse: Boolean = false): T = match(NullCheckMatcher<T>(inverse))
842852
inline fun <reified T : Any, R : T> ofType(cls: KClass<R>): T = match(OfTypeMatcher<T>(cls))
853+
843854
/**
844855
* Checks if the value belongs to the type.
845856
*/
@@ -2182,6 +2193,48 @@ class MockKVerificationScope(
21822193
}
21832194
}
21842195

2196+
/**
2197+
* Part of DSL. Additional operations for call count verification scope.
2198+
*/
2199+
class MockKCallCountVerificationScope {
2200+
operator fun Int.times(verifyBlock: MockKVerificationScope.() -> Unit) {
2201+
MockKGateway.implementation().verifier.verify(
2202+
VerificationParameters(Ordering.UNORDERED, min = this, max = this, inverse = false, timeout = 0),
2203+
verifyBlock,
2204+
null
2205+
)
2206+
}
2207+
2208+
operator fun IntRange.times(verifyBlock: MockKVerificationScope.() -> Unit) {
2209+
MockKGateway.implementation().verifier.verify(
2210+
VerificationParameters(Ordering.UNORDERED, min = this.first, max = this.last, inverse = false, timeout = 0),
2211+
verifyBlock,
2212+
null
2213+
)
2214+
}
2215+
}
2216+
2217+
/**
2218+
* Part of DSL. Additional operations for coroutine call count verification scope.
2219+
*/
2220+
class MockKCallCountCoVerificationScope {
2221+
operator fun Int.times(verifyBlock: suspend MockKVerificationScope.() -> Unit) {
2222+
MockKGateway.implementation().verifier.verify(
2223+
VerificationParameters(Ordering.UNORDERED, min = this, max = this, inverse = false, timeout = 0),
2224+
null,
2225+
verifyBlock
2226+
)
2227+
}
2228+
2229+
operator fun IntRange.times(verifyBlock: suspend MockKVerificationScope.() -> Unit) {
2230+
MockKGateway.implementation().verifier.verify(
2231+
VerificationParameters(Ordering.UNORDERED, min = this.first, max = this.last, inverse = false, timeout = 0),
2232+
null,
2233+
verifyBlock
2234+
)
2235+
}
2236+
}
2237+
21852238
/**
21862239
* Part of DSL. Object to represent phrase "wasNot Called"
21872240
*/
@@ -2675,7 +2728,7 @@ class CapturingSlot<T : Any?> {
26752728
* For init state (not yet captured) returns false.
26762729
*/
26772730
val isNull
2678-
get() = when(val value = capturedValue) {
2731+
get() = when (val value = capturedValue) {
26792732
is CapturedValue.Value -> value.value == null
26802733
is CapturedValue.NotYetCaptured -> false
26812734
}

modules/mockk/api/mockk.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public final class io/mockk/MockKKt {
3838
public static synthetic fun coVerify$default (Lio/mockk/Ordering;ZIIIJLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
3939
public static final fun coVerifyAll (ZLkotlin/jvm/functions/Function2;)V
4040
public static synthetic fun coVerifyAll$default (ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
41+
public static final fun coVerifyCount (Lkotlin/jvm/functions/Function1;)V
4142
public static final fun coVerifyOrder (ZLkotlin/jvm/functions/Function2;)V
4243
public static synthetic fun coVerifyOrder$default (ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
4344
public static final fun coVerifySequence (ZLkotlin/jvm/functions/Function2;)V
@@ -72,6 +73,7 @@ public final class io/mockk/MockKKt {
7273
public static synthetic fun verify$default (Lio/mockk/Ordering;ZIIIJLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
7374
public static final fun verifyAll (ZLkotlin/jvm/functions/Function1;)V
7475
public static synthetic fun verifyAll$default (ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
76+
public static final fun verifyCount (Lkotlin/jvm/functions/Function1;)V
7577
public static final fun verifyOrder (ZLkotlin/jvm/functions/Function1;)V
7678
public static synthetic fun verifyOrder$default (ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
7779
public static final fun verifySequence (ZLkotlin/jvm/functions/Function1;)V

modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ fun coVerify(
261261
* @see verify
262262
* @see verifyOrder
263263
* @see verifySequence
264+
* @see verifyCount
264265
*
265266
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
266267
*/
@@ -278,6 +279,7 @@ fun verifyAll(
278279
* @see verify
279280
* @see verifyAll
280281
* @see verifySequence
282+
* @see verifyCount
281283
*
282284
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
283285
*/
@@ -295,6 +297,7 @@ fun verifyOrder(
295297
* @see verify
296298
* @see verifyOrder
297299
* @see verifyAll
300+
* @see verifyCount
298301
*
299302
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
300303
*/
@@ -305,6 +308,20 @@ fun verifySequence(
305308
MockKDsl.internalVerifySequence(inverse, verifyBlock)
306309
}
307310

311+
/**
312+
* Verifies that calls and their count
313+
*
314+
* @see coVerifyCount Coroutine version
315+
* @see verify
316+
* @see verifyOrder
317+
* @see verifyAll
318+
* @see verifySequence
319+
*
320+
*/
321+
fun verifyCount(verifyBlock: MockKCallCountVerificationScope.() -> Unit) = MockK.useImpl {
322+
MockKCallCountVerificationScope().verifyBlock()
323+
}
324+
308325
/**
309326
* Verifies that all calls inside [verifyBlock] happened. **Does not** verify any order. Coroutine version
310327
*
@@ -314,6 +331,7 @@ fun verifySequence(
314331
* @see coVerify
315332
* @see coVerifyOrder
316333
* @see coVerifySequence
334+
* @see coVerifyCount
317335
*
318336
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
319337
*/
@@ -332,6 +350,7 @@ fun coVerifyAll(
332350
* @see coVerify
333351
* @see coVerifyAll
334352
* @see coVerifySequence
353+
* @see coVerifyCount
335354
*
336355
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
337356
*/
@@ -350,6 +369,7 @@ fun coVerifyOrder(
350369
* @see coVerify
351370
* @see coVerifyOrder
352371
* @see coVerifyAll
372+
* @see coVerifyCount
353373
*
354374
* @param inverse when true, the verification will check that the behaviour specified did **not** happen
355375
*/
@@ -360,6 +380,20 @@ fun coVerifySequence(
360380
MockKDsl.internalCoVerifySequence(inverse, verifyBlock)
361381
}
362382

383+
/**
384+
* Verifies that calls and their count. Coroutine version
385+
*
386+
* @see verifyCount
387+
* @see coVerify
388+
* @see coVerifyOrder
389+
* @see coVerifyAll
390+
* @see coVerifySequence
391+
*
392+
*/
393+
fun coVerifyCount(verifyBlock: MockKCallCountCoVerificationScope.() -> Unit) = MockK.useImpl {
394+
MockKCallCountCoVerificationScope().verifyBlock()
395+
}
396+
363397
/**
364398
* Exclude calls from recording
365399
*

modules/mockk/src/commonTest/kotlin/io/mockk/it/CaptureSubclassVerificationTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.mockk.just
66
import io.mockk.mockk
77
import io.mockk.slot
88
import io.mockk.verify
9+
import io.mockk.verifyCount
910
import io.mockk.verifyOrder
1011
import io.mockk.verifySequence
1112
import kotlin.test.Test
@@ -77,4 +78,22 @@ class CaptureSubclassVerificationTest {
7778
assertTrue(slot.isCaptured)
7879
assertIs<Subclass2>(slot.captured)
7980
}
81+
82+
@Test
83+
fun `test count`() {
84+
val service = mockk<Service> {
85+
every { method(any()) } just Runs
86+
}
87+
88+
service.method(Subclass1())
89+
service.method(Subclass2())
90+
91+
val slot = slot<Subclass2>()
92+
verifyCount {
93+
2 * { service.method(any()) }
94+
1 * { service.method(capture(slot)) }
95+
}
96+
assertTrue(slot.isCaptured)
97+
assertIs<Subclass2>(slot.captured)
98+
}
8099
}

modules/mockk/src/commonTest/kotlin/io/mockk/it/CoVerifyTest.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package io.mockk.it
22

3-
import io.mockk.*
4-
import kotlinx.coroutines.coroutineScope
3+
import io.mockk.InternalPlatformDsl
4+
import io.mockk.coEvery
5+
import io.mockk.coVerify
6+
import io.mockk.coVerifyCount
7+
import io.mockk.coVerifyOrder
8+
import io.mockk.coVerifySequence
9+
import io.mockk.mockk
510
import kotlin.test.Test
611
import kotlin.test.assertEquals
12+
import kotlinx.coroutines.coroutineScope
713

814
class CoVerifyTest {
915
class MockCls {
@@ -12,7 +18,7 @@ class CoVerifyTest {
1218

1319
val mock = mockk<MockCls>()
1420

15-
fun doCalls() {
21+
private fun doCalls() {
1622
coEvery { mock.op(5) } returns 1
1723
coEvery { mock.op(6) } returns 2
1824
coEvery { mock.op(7) } returns 3
@@ -156,4 +162,18 @@ class CoVerifyTest {
156162
mock.op(7)
157163
}
158164
}
165+
166+
@Test
167+
fun verifyCount() {
168+
doCalls()
169+
170+
coVerifyCount {
171+
0 * { mock.op(4) } // not called
172+
1 * { mock.op(5) } // called
173+
(0..Int.MAX_VALUE) * { mock.op(6) } // called
174+
(1..1) * { mock.op(7) } // called
175+
(0..0) * { mock.op(8) } // not called
176+
(0..1) * { mock.op(9) } // not called
177+
}
178+
}
159179
}

modules/mockk/src/commonTest/kotlin/io/mockk/it/VerificationErrorsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class VerificationErrorsTest {
135135
}
136136

137137
@Test
138-
fun callsNotMatchinVerificationSequence() {
138+
fun callsNotMatchingVerificationSequence() {
139139
expectVerificationError("calls are not exactly matching verification sequence", "MockCls.otherOp") {
140140
every { mock.otherOp(1, any()) } answers { 2 + firstArg<Int>() }
141141

0 commit comments

Comments
 (0)