diff --git a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt index c467cd206..9af987c94 100644 --- a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt +++ b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt @@ -700,28 +700,92 @@ open class MockKMatcherScope( inline fun matchNullable(noinline matcher: (T?) -> Boolean): T = match(FunctionWithNullableArgMatcher(matcher, T::class)) + /** + * Matches if the value is equal to the provided [value] via the `deepEquals` function. + * + * If matchers are being used, the eq argument matcher must be used to match literal values. + * When no matchers are used, literal arguments are automatically matched using eq. + * + * @sample + * class Calculator { + * fun add(a: Int, b: Int) = a + b + * } + * + * val calculator = mockk() + * every { calculator.add(any(), eq(5)) } returns 44 + * every { calculator.add(1, 2) returns 55 + */ inline fun eq(value: T, inverse: Boolean = false): T = match(EqMatcher(value, inverse = inverse)) - + /** + * Matches if the value is not equal to the provided [value] via the `deepEquals` function. + */ inline fun neq(value: T): T = eq(value, true) + + /** + * Matches if the value is equal to the provided [value] via reference comparison. + */ inline fun refEq(value: T, inverse: Boolean = false): T = match(EqMatcher(value, ref = true, inverse = inverse)) - + /** + * Matches if the value is not equal to the provided [value] via reference comparison. + */ inline fun nrefEq(value: T) = refEq(value, true) + /** + * Matches any argument. + */ inline fun any(): T = match(ConstantMatcher(true)) + inline fun capture(lst: MutableList): T = match(CaptureMatcher(lst, T::class)) + /** + * Captures a non-nullable value to a [CapturingSlot]. + * + * @see [io.mockk.slot] to create capturing slot. + * @see [captureNullable] for nullable arguments. + * @sample + * interface FileNetwork { + * fun download(name: String): File + * } + * + * val network = mockk() + * val slot = slot() + * + * every { network.download(capture(slot)) } returns mockk() + * + * network.download("testfile") + * // slot.captured is now "testfile" + */ inline fun capture(lst: CapturingSlot): T = match(CapturingSlotMatcher(lst, T::class)) + /** + * Captures a nullable value to a [CapturingSlot]. + * + * @see [io.mockk.slot] to create capturing slot. + * @see [capture] for non-nullable arguments. + */ inline fun captureNullable(lst: MutableList): T? = match(CaptureNullableMatcher(lst, T::class)) + /** + * Matches if the value is equal to the provided [value] via the `compareTo` function. + */ inline fun > cmpEq(value: T): T = match(ComparingMatcher(value, 0, T::class)) + /** + * Matches if the value is more than the provided [value] via the `compareTo` function. + * @param andEquals matches more than or equal to + */ inline fun > more(value: T, andEquals: Boolean = false): T = match(ComparingMatcher(value, if (andEquals) 2 else 1, T::class)) - + /** + * Matches if the value is less than the provided [value] via the `compareTo` function. + * @param andEquals matches less than or equal to + */ inline fun > less(value: T, andEquals: Boolean = false): T = match(ComparingMatcher(value, if (andEquals) -2 else -1, T::class)) + /** + * Matches if the value is in range via the `compareTo` function. + */ inline fun > range( from: T, to: T, @@ -729,11 +793,29 @@ open class MockKMatcherScope( toInclusive: Boolean = true ): T = and(more(from, fromInclusive), less(to, toInclusive)) + /** + * Combines two matchers via a logical and. + */ inline fun and(left: T, right: T): T = match(AndOrMatcher(true, left, right)) + /** + * Combines two matchers via a logical or. + */ inline fun or(left: T, right: T): T = match(AndOrMatcher(false, left, right)) + /** + * Negates the matcher. + */ inline fun not(value: T): T = match(NotMatcher(value)) + + /** + * Checks if the value is null. + * + * @param inverse checks if the value is not-null instead + */ inline fun isNull(inverse: Boolean = false): T = match(NullCheckMatcher(inverse)) inline fun ofType(cls: KClass): T = match(OfTypeMatcher(cls)) + /** + * Checks if the value belongs to the type. + */ inline fun ofType(): T = match(OfTypeMatcher(T::class)) inline fun anyVararg() = varargAllNullable { true } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt index 6131b5af9..7803cd5f1 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt @@ -5,15 +5,32 @@ package io.mockk import kotlin.reflect.KClass /** - * Builds a new mock for specified class + * Builds a new mock for specified class. + * + * A mock is a fake version of a class that replaces all the methods with fake implementations. + * + * By default, every method that you wish to mock should be stubbed using [every]. + * Otherwise, it will throw when called so you know if you forgot to mock a method. + * If [relaxed] or [relaxUnitFun] is set to true, methods will automatically be stubbed. * * @param name mock name - * @param relaxed allows creation with no specific behaviour - * @param moreInterfaces additional interfaces for this mockk to implement - * @param relaxUnitFun allows creation with no specific behaviour for Unit function - * @param block block to execute after mock is created with mock as a receiver + * @param relaxed allows creation with no specific behaviour. Unstubbed methods will not throw. + * @param moreInterfaces additional interfaces for this mockk to implement, in addition to the specified class. + * @param relaxUnitFun allows creation with no specific behaviour for Unit function. + * Unstubbed methods that return [Unit] will not throw, while other methods will still throw unless they are stubbed. + * @param block block to execute after mock is created with mock as a receiver. Similar to using [apply] on the mock object. * - + * @sample + * interface Navigator { + * val currentLocation: String + * fun navigateTo(newLocation: String): Unit + * } + * + * val navigator = mockk() + * every { navigator.currentLocation } returns "Home" + * + * println(navigator.currentLocation) // prints "Home" + * navigator.navigateTo("Store") // throws an error */ inline fun mockk( name: String? = null, @@ -34,14 +51,13 @@ inline fun mockk( /** * Builds a new spy for specified class. Initializes object via default constructor. * - * A spy is a special kind of mockk that enables a mix of mocked behaviour and real behaviour. - * A part of the behaviour may be mocked, but any non-mocked behaviour will call the original method. + * A spy is a special kind of [mockk] that enables a mix of mocked behaviour and real behaviour. + * A part of the behaviour may be mocked using [every], but any non-mocked behaviour will call the original method. * * @param name spyk name - * @param moreInterfaces additional interfaces for this spyk to implement - * @param recordPrivateCalls allows this spyk to record any private calls, enabling a verification - * @param block block to execute after spyk is created with spyk as a receiver - * + * @param moreInterfaces additional interfaces for this spyk to implement, in addition to the specified class. + * @param recordPrivateCalls allows this spyk to record any private calls, enabling a verification. + * @param block block to execute after spyk is created with spyk as a receiver. Similar to using [apply] on the spyk object. */ inline fun spyk( name: String? = null, @@ -60,10 +76,14 @@ inline fun spyk( /** * Builds a new spy for specified class, copying fields from [objToCopy]. * - * A spy is a special kind of mockk that enables a mix of mocked behaviour and real behaviour. - * A part of the behaviour may be mocked, but any non-mocked behaviour will call the original method. + * A spy is a special kind of [mockk] that enables a mix of mocked behaviour and real behaviour. + * A part of the behaviour may be mocked using [every], but any non-mocked behaviour will call the original method. * - */ + * @param name spyk name + * @param moreInterfaces additional interfaces for this spyk to implement, in addition to the specified class. + * @param recordPrivateCalls allows this spyk to record any private calls, enabling a verification. + * @param block block to execute after spyk is created with spyk as a receiver. Similar to using [apply] on the spyk object. + */ inline fun spyk( objToCopy: T, name: String? = null, @@ -81,8 +101,23 @@ inline fun spyk( } /** - * Creates new capturing slot + * Creates new capturing slot. + * + * Slots allow you to capture what arguments a mocked method is called with. + * When mocking a method using [every], pass the slot wrapped with the [MockKMatcherScope.capture] function in place of a method argument or [MockKMatcherScope.any]. + * + * @sample + * interface FileNetwork { + * fun download(name: String): File + * } * + * val network = mockk() + * val slot = slot() + * + * every { network.download(capture(slot)) } returns mockk() + * + * network.download("testfile") + * // slot.captured is now "testfile" */ inline fun slot() = MockK.useImpl { MockKDsl.internalSlot() @@ -93,6 +128,16 @@ inline fun slot() = MockK.useImpl { * * Used to define what behaviour is going to be mocked. * + * @sample + * interface Navigator { + * val currentLocation: String + * } + * + * val navigator = mockk() + * every { navigator.currentLocation } returns "Home" + * + * println(navigator.currentLocation) // prints "Home" + * @see [coEvery] Coroutine version. */ fun every(stubBlock: MockKMatcherScope.() -> T): MockKStubScope = MockK.useImpl { MockKDsl.internalEvery(stubBlock) @@ -102,12 +147,20 @@ fun every(stubBlock: MockKMatcherScope.() -> T): MockKStubScope = Mock * Stub block to return Unit result. Part of DSL. * * Used to define what behaviour is going to be mocked. + * Acts as a shortcut for `every { ... } returns Unit`. + * + * @see [every] + * @see [coJustRun] Coroutine version. + * @sample + * every { logger.log(any()) } returns Unit + * every { logger.log(any()) } just Runs + * justRun { logger.log(any()) } */ fun justRun(stubBlock: MockKMatcherScope.() -> Unit) = every(stubBlock) just Runs /** * Starts a block of stubbing for coroutines. Part of DSL. - * Similar to [every] + * Similar to [every], but works with suspend functions. * * Used to define what behaviour is going to be mocked. * @see [every] @@ -118,9 +171,11 @@ fun coEvery(stubBlock: suspend MockKMatcherScope.() -> T): MockKStubScope Unit) = coEvery(stubBlock) just Runs @@ -133,7 +188,7 @@ fun coJustRun(stubBlock: suspend MockKMatcherScope.() -> Unit) = coEvery(stubBlo fun coJustAwait(stubBlock: suspend MockKMatcherScope.() -> Unit) = coEvery(stubBlock) just Awaits /** - * Verifies that calls were made in the past. Part of DSL + * Verifies that calls were made in the past. Part of DSL. * * @param ordering how the verification should be ordered * @param inverse when true, the verification will check that the behaviour specified did **not** happen @@ -144,6 +199,12 @@ fun coJustAwait(stubBlock: suspend MockKMatcherScope.() -> Unit) = coEvery(stubB * passed or timeout is reached. * @param verifyBlock code block containing at least 1 call to verify * + * @sample + * val navigator = mockk(relaxed = true) + * + * navigator.navigateTo("Park") + * verify { navigator.navigateTo(any()) } + * @see [coVerify] Coroutine version */ fun verify( ordering: Ordering = Ordering.UNORDERED, @@ -194,8 +255,9 @@ fun coVerify( /** * Verifies that all calls inside [verifyBlock] happened. **Does not** verify any order. * - * If ordering is important, use [verifyOrder] + * If ordering is important, use [verifyOrder]. * + * @see coVerifyAll Coroutine version * @see verify * @see verifyOrder * @see verifySequence @@ -212,13 +274,13 @@ fun verifyAll( /** * Verifies that all calls inside [verifyBlock] happened, checking that they happened in the order declared. * + * @see coVerifyOrder Coroutine version * @see verify * @see verifyAll * @see verifySequence * * @param inverse when true, the verification will check that the behaviour specified did **not** happen - * - */ + */ fun verifyOrder( inverse: Boolean = false, verifyBlock: MockKVerificationScope.() -> Unit @@ -229,13 +291,13 @@ fun verifyOrder( /** * Verifies that all calls inside [verifyBlock] happened, and no other call was made to those mocks * + * @see coVerifySequence Coroutine version * @see verify * @see verifyOrder * @see verifyAll * * @param inverse when true, the verification will check that the behaviour specified did **not** happen - * - */ + */ fun verifySequence( inverse: Boolean = false, verifyBlock: MockKVerificationScope.() -> Unit @@ -246,8 +308,9 @@ fun verifySequence( /** * Verifies that all calls inside [verifyBlock] happened. **Does not** verify any order. Coroutine version * - * If ordering is important, use [verifyOrder] + * If ordering is important, use [coVerifyOrder] * + * @see verifyAll * @see coVerify * @see coVerifyOrder * @see coVerifySequence @@ -265,13 +328,13 @@ fun coVerifyAll( * Verifies that all calls inside [verifyBlock] happened, checking that they happened in the order declared. * Coroutine version. * + * @see verifyOrder * @see coVerify * @see coVerifyAll * @see coVerifySequence * * @param inverse when true, the verification will check that the behaviour specified did **not** happen - * - */ + */ fun coVerifyOrder( inverse: Boolean = false, verifyBlock: suspend MockKVerificationScope.() -> Unit @@ -280,15 +343,16 @@ fun coVerifyOrder( } /** - * Verifies that all calls inside [verifyBlock] happened, and no other call was made to those mocks + * Verifies that all calls inside [verifyBlock] happened, and no other call was made to those mocks. + * Coroutine version. * + * @see verifySequence * @see coVerify * @see coVerifyOrder * @see coVerifyAll * * @param inverse when true, the verification will check that the behaviour specified did **not** happen - * - */ + */ fun coVerifySequence( inverse: Boolean = false, verifyBlock: suspend MockKVerificationScope.() -> Unit @@ -300,6 +364,7 @@ fun coVerifySequence( * Exclude calls from recording * * @param current if current recorded calls should be filtered out + * @see [coExcludeRecords] Coroutine version. */ fun excludeRecords( current: Boolean = true, @@ -312,6 +377,7 @@ fun excludeRecords( * Exclude calls from recording for a suspend block * * @param current if current recorded calls should be filtered out + * @see [excludeRecords] */ fun coExcludeRecords( current: Boolean = true, @@ -400,7 +466,18 @@ inline fun mockkClass( /** * Builds an Object mock. Any mocks of this exact object are cancelled before it's mocked. * - */ + * This lets you mock object methods with [every]. + * + * @sample + * object CalculatorObject { + * fun add(a: Int, b: Int) = a + b + * } + * + * mockkObject(CalculatorObject) + * every { ObjBeingMocked.add(1, 2) } returns 55 + * + * @see [unmockkObject] To manually cancel mock + */ inline fun mockkObject(vararg objects: Any, recordPrivateCalls: Boolean = false) = MockK.useImpl { MockKDsl.internalMockkObject(*objects, recordPrivateCalls = recordPrivateCalls) } @@ -427,6 +504,7 @@ inline fun mockkObject(vararg objects: Any, recordPrivateCalls: Boolean = false, /** * Builds a static mock. Any mocks of this exact class are cancelled before it's mocked * + * @see [unmockkStatic] To manually cancel mock */ inline fun mockkStatic(vararg classes: KClass<*>) = MockK.useImpl { MockKDsl.internalMockkStatic(*classes) @@ -435,6 +513,7 @@ inline fun mockkStatic(vararg classes: KClass<*>) = MockK.useImpl { /** * Builds a static mock. Old static mocks of same classes are cancelled before. * + * @see [unmockkStatic] To manually cancel mock */ inline fun mockkStatic(vararg classes: String) = MockK.useImpl { MockKDsl.internalMockkStatic(*classes.map { InternalPlatformDsl.classForName(it) as KClass<*> }.toTypedArray()) @@ -497,6 +576,20 @@ inline fun mockkStatic(vararg classes: String, block: () -> Unit) { /** * Builds a constructor mock. Old constructor mocks of same classes are cancelled before. + * + * Once used, every constructor of the given class will start returning a singleton that can be mocked. + * Rather than building a new instance every time the constructor is called, + * MockK generates a singleton and always returns the same instance. + * This will apply to all constructors for a given class, there is no way to distinguish between them. + * + * @see [unmockkConstructor] To manually cancel mock + * @sample + * class ClassToTest { + * private val log = Logger() + * } + * + * mockkConstructor(Logger::class) + * // ClassToTest.log will now use a mock instance */ inline fun mockkConstructor( vararg classes: KClass<*>,