diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt index 3728b1bc..708dadcd 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt @@ -31,14 +31,14 @@ import org.mockito.BDDMockito import org.mockito.BDDMockito.BDDMyOngoingStubbing import org.mockito.BDDMockito.Then import org.mockito.invocation.InvocationOnMock -import org.mockito.kotlin.internal.SuspendableAnswer +import org.mockito.kotlin.internal.CoroutineAwareAnswer import org.mockito.stubbing.Answer import kotlin.reflect.KClass /** * Alias for [BDDMockito.given]. */ -fun given(methodCall: T): BDDMockito.BDDMyOngoingStubbing { +fun given(methodCall: T): BDDMyOngoingStubbing { return BDDMockito.given(methodCall) } @@ -55,14 +55,14 @@ fun given(methodCall: () -> T): BDDMyOngoingStubbing { * Warning: Only last method call can be stubbed in the function. * other method calls are ignored! */ -fun givenBlocking(methodCall: suspend CoroutineScope.() -> T): BDDMockito.BDDMyOngoingStubbing { +fun givenBlocking(methodCall: suspend CoroutineScope.() -> T): BDDMyOngoingStubbing { return runBlocking { BDDMockito.given(methodCall()) } } /** * Alias for [BDDMockito.then]. */ -fun then(mock: T): BDDMockito.Then { +fun then(mock: T): Then { return BDDMockito.then(mock) } @@ -77,36 +77,36 @@ fun Then.shouldBlocking(f: suspend T.() -> R): R { /** * Alias for [BDDMyOngoingStubbing.will] * */ -infix fun BDDMyOngoingStubbing.will(value: Answer): BDDMockito.BDDMyOngoingStubbing { - return will(value) +infix fun BDDMyOngoingStubbing.will(answer: Answer): BDDMyOngoingStubbing { + return will(answer) } /** - * Alias for [BBDMyOngoingStubbing.willAnswer], accepting a lambda. + * Alias for [BDDMyOngoingStubbing.willAnswer], accepting a lambda. */ -infix fun BDDMyOngoingStubbing.willAnswer(value: (InvocationOnMock) -> T?): BDDMockito.BDDMyOngoingStubbing { - return willAnswer { value(it) } +infix fun BDDMyOngoingStubbing.willAnswer(answer: (InvocationOnMock) -> T?): BDDMyOngoingStubbing { + return willAnswer { answer(it) } } /** - * Alias for [BBDMyOngoingStubbing.willAnswer], accepting a suspend lambda. + * Alias for [BDDMyOngoingStubbing.willAnswer], accepting a suspend lambda. */ -infix fun BDDMyOngoingStubbing.willSuspendableAnswer(value: suspend (InvocationOnMock) -> T?): BDDMockito.BDDMyOngoingStubbing { - return willAnswer(SuspendableAnswer(value)) +infix fun BDDMyOngoingStubbing.willSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): BDDMyOngoingStubbing { + return willAnswer(CoroutineAwareAnswer(answer)) } /** - * Alias for [BBDMyOngoingStubbing.willReturn]. + * Alias for [BDDMyOngoingStubbing.willReturn]. */ -infix fun BDDMyOngoingStubbing.willReturn(value: () -> T): BDDMockito.BDDMyOngoingStubbing { +infix fun BDDMyOngoingStubbing.willReturn(value: () -> T): BDDMyOngoingStubbing { return willReturn(value()) } /** - * Alias for [BBDMyOngoingStubbing.willThrow]. + * Alias for [BDDMyOngoingStubbing.willThrow]. */ -infix fun BDDMyOngoingStubbing.willThrow(value: () -> Throwable): BDDMockito.BDDMyOngoingStubbing { - return willThrow(value()) +infix fun BDDMyOngoingStubbing.willThrow(throwable: () -> Throwable): BDDMyOngoingStubbing { + return willThrow(throwable()) } /** @@ -114,8 +114,8 @@ infix fun BDDMyOngoingStubbing.willThrow(value: () -> Throwable): BDDMock * * Alias for [BDDMyOngoingStubbing.willThrow] */ -infix fun BDDMyOngoingStubbing.willThrow(t: KClass): BDDMyOngoingStubbing { - return willThrow(t.java) +infix fun BDDMyOngoingStubbing.willThrow(clazz: KClass): BDDMyOngoingStubbing { + return willThrow(clazz.java) } /** @@ -124,19 +124,19 @@ infix fun BDDMyOngoingStubbing.willThrow(t: KClass): BDDMy * Alias for [BDDMyOngoingStubbing.willThrow] */ fun BDDMyOngoingStubbing.willThrow( - t: KClass, - vararg ts: KClass + clazz: KClass, + vararg otherClasses: KClass ): BDDMyOngoingStubbing { - return willThrow(t.java, *ts.map { it.java }.toTypedArray()) + return willThrow(clazz.java, *otherClasses.map { it.java }.toTypedArray()) } /** * Sets consecutive return values to be returned when the method is called. * Same as [BDDMyOngoingStubbing.willReturn], but accepts list instead of varargs. */ -inline infix fun BDDMyOngoingStubbing.willReturnConsecutively(ts: List): BDDMyOngoingStubbing { +inline infix fun BDDMyOngoingStubbing.willReturnConsecutively(values: List): BDDMyOngoingStubbing { return willReturn( - ts[0], - *ts.drop(1).toTypedArray() + values[0], + *values.drop(1).toTypedArray() ) } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt index b433b761..071cce2d 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt @@ -28,7 +28,7 @@ package org.mockito.kotlin import org.mockito.invocation.InvocationOnMock class KInvocationOnMock( - private val invocationOnMock: InvocationOnMock + val invocationOnMock: InvocationOnMock ) : InvocationOnMock by invocationOnMock { operator fun component1(): T = invocationOnMock.getArgument(0) @@ -36,4 +36,59 @@ class KInvocationOnMock( operator fun component3(): T = invocationOnMock.getArgument(2) operator fun component4(): T = invocationOnMock.getArgument(3) operator fun component5(): T = invocationOnMock.getArgument(4) + + /** + * The first argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun first(): T = invocationOnMock.getArgument(0) + + /** + * The second argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun second(): T = invocationOnMock.getArgument(1) + + /** + * The third argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun third(): T = invocationOnMock.getArgument(2) + + /** + * The fourth argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun fourth(): T = invocationOnMock.getArgument(3) + + /** + * The fifth argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun fifth(): T = invocationOnMock.getArgument(4) + + /** + * The last argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun last(): T { + val size = invocationOnMock.arguments.size + require(size >= 1) { "The invocation was expected to have at least 1 argument but got none." } + return invocationOnMock.getArgument(size - 1) + } + + /** + * The single argument. + * @throws IndexOutOfBoundsException if the argument is not available. + */ + inline fun single(): T { + val size = invocationOnMock.arguments.size + require(size == 1) { "The invocation was expected to have exactly 1 argument but got $size." } + return first() + } + + /** + * The all arguments. + */ + fun all(): List = invocationOnMock.arguments.toList() } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt index 31a7c75c..0f92068e 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt @@ -25,21 +25,25 @@ package org.mockito.kotlin -import org.mockito.kotlin.internal.createInstance import kotlinx.coroutines.runBlocking import org.mockito.Mockito import org.mockito.exceptions.misusing.NotAMockException +import org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress +import org.mockito.kotlin.internal.createInstance import org.mockito.stubbing.OngoingStubbing import org.mockito.stubbing.Stubber import kotlin.reflect.KClass -inline fun stubbing( - mock: T, - stubbing: KStubbing.(T) -> Unit -) { +/** + * Apply stubbing on the given mock. The stubbed behavior of the mock can then be specified in a supplied lambda. + */ +inline fun stubbing(mock: T, stubbing: KStubbing.(T) -> Unit) { KStubbing(mock).stubbing(mock) } +/** + * Apply stubbing on the given mock. The stubbed behavior of the mock can then be specified in a supplied lambda. + */ inline fun T.stub(stubbing: KStubbing.(T) -> Unit): T { return apply { KStubbing(this).stubbing(this) } } @@ -49,12 +53,57 @@ class KStubbing(val mock: T) { if (!mockingDetails(mock).isMock) throw NotAMockException("Stubbing target is not a mock!") } - fun on(methodCall: R): OngoingStubbing = Mockito.`when`(methodCall) + /** + * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. + * + * Simply put: `When the x method is called then return y` + */ + fun on(methodCall: R): OngoingStubbing = mockitoWhen(methodCall) + + /** + * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. + * + * Simply put: `When the x method is called then return y` + */ + fun on(methodCall: T.() -> R): OngoingStubbing { + return try { + mockitoWhen(mock.methodCall()) + } catch (e: NullPointerException) { + throw MockitoKotlinException( + "NullPointerException thrown when stubbing.\nThis may be due to two reasons:\n\t- The method you're trying to stub threw an NPE: look at the stack trace below;\n\t- You're trying to stub a generic method: try `onGeneric` instead.", + e + ) + } + } + + /** + * Enables stubbing suspend functions. Use it when you want the mock to return particular value when particular suspend function is called. + * + * Simply put: `When the x suspend function is called then return y` + */ + fun onBlocking(suspendFunctionCall: suspend T.() -> R): OngoingStubbing { + return runBlocking { mockitoWhen(mock.suspendFunctionCall()) } + } + + /** + * Enables stubbing generic methods with return type [R]. Use it when you want the mock to return particular value when particular method is called. + * + * Simply put: `When the x method is called then return y of type R` + */ + inline fun onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing { + return onGeneric(methodCall, R::class) + } - fun onGeneric(methodCall: T.() -> R?, c: KClass): OngoingStubbing { + + /** + * Enables stubbing generic methods with return type [R]. Use it when you want the mock to return particular value when particular method is called. + * + * Simply put: `When the x method is called then return y of type R` + */ + fun onGeneric(methodCall: T.() -> R?, c: KClass): OngoingStubbing { val r = try { mock.methodCall() - } catch (e: NullPointerException) { + } catch (_: NullPointerException) { // An NPE may be thrown by the Kotlin type system when the MockMethodInterceptor returns a // null value for a non-nullable generic type. // We catch this NPE to return a valid instance. @@ -62,31 +111,27 @@ class KStubbing(val mock: T) { // the wanted changes. createInstance(c) } - return Mockito.`when`(r) + return mockitoWhen(r) } - inline fun onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing { - return onGeneric(methodCall, R::class) + /** + * Completes stubbing a method, by addressing the method call to apply a given stubbed [org.mockito.stubbing.Answer] on. + * Use it when you want the mock to return particular value when particular method is called. + * + * Simply put: `Return y when the x method is called` + */ + fun Stubber.on(methodCall: T.() -> Unit) { + this.`when`(mock).methodCall() } - fun on(methodCall: T.() -> R): OngoingStubbing { - return try { - Mockito.`when`(mock.methodCall()) - } catch (e: NullPointerException) { - throw MockitoKotlinException( - "NullPointerException thrown when stubbing.\nThis may be due to two reasons:\n\t- The method you're trying to stub threw an NPE: look at the stack trace below;\n\t- You're trying to stub a generic method: try `onGeneric` instead.", - e - ) - } - } + private fun mockitoWhen(methodCall: R): OngoingStubbing { + val ongoingStubbing = Mockito.`when`(methodCall) - fun KStubbing.onBlocking( - m: suspend T.() -> R - ): OngoingStubbing { - return runBlocking { Mockito.`when`(mock.m()) } - } + if (ongoingStubbing.getMock() != mock) { + mockingProgress().reset() + throw IllegalArgumentException("Stubbing of another mock is not allowed") + } - fun Stubber.on(methodCall: T.() -> Unit) { - this.`when`(mock).methodCall() + return ongoingStubbing } } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt index 39c6dcb2..cb1a5f60 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt @@ -28,8 +28,13 @@ package org.mockito.kotlin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import org.mockito.Mockito +import org.mockito.internal.stubbing.answers.CallsRealMethods +import org.mockito.internal.stubbing.answers.Returns +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.internal.stubbing.answers.ThrowsExceptionForClassType +import org.mockito.kotlin.internal.CoroutineAwareAnswer import org.mockito.kotlin.internal.KAnswer -import org.mockito.kotlin.internal.SuspendableAnswer +import org.mockito.kotlin.internal.CoroutineAwareAnswer.Companion.wrapAsCoroutineAwareAnswer import org.mockito.stubbing.Answer import org.mockito.stubbing.OngoingStubbing import kotlin.reflect.KClass @@ -37,11 +42,19 @@ import kotlin.reflect.KClass /** * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. * - * Alias for [Mockito.when]. + * Alias for [org.mockito.Mockito.when]. */ -@Suppress("NOTHING_TO_INLINE") -inline fun whenever(methodCall: T): OngoingStubbing { - return Mockito.`when`(methodCall)!! +fun whenever(methodCall: () -> T): OngoingStubbing { + return whenever(methodCall()) +} + +/** + * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. + * + * Alias for [org.mockito.Mockito.when]. + */ +fun whenever(methodCall: T): OngoingStubbing { + return Mockito.`when`(methodCall) } /** @@ -51,16 +64,23 @@ inline fun whenever(methodCall: T): OngoingStubbing { * other method calls are ignored! */ fun wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing { - return runBlocking { Mockito.`when`(methodCall()) } + return runBlocking { whenever(methodCall()) } } /** * Sets a return value to be returned when the method is called. * - * Alias for [OngoingStubbing.thenReturn]. + * Alias for [thenReturn]. */ -infix fun OngoingStubbing.doReturn(t: T): OngoingStubbing { - return thenReturn(t) +infix fun OngoingStubbing.doReturn(value: T): OngoingStubbing { + return doAnswerInternal(Returns(value)) +} + +/** + * Sets a return value to be returned when the method is called. + */ +infix fun OngoingStubbing.thenReturn(value: T): OngoingStubbing { + return doAnswerInternal(Returns(value)) } /** @@ -68,18 +88,22 @@ infix fun OngoingStubbing.doReturn(t: T): OngoingStubbing { * * Alias for [OngoingStubbing.thenReturn]. */ -fun OngoingStubbing.doReturn(t: T, vararg ts: T): OngoingStubbing { - return thenReturn(t, *ts) +fun OngoingStubbing.doReturn(value: T, vararg otherValues: T): OngoingStubbing { + return doAnswerInternal(listOf(value, *otherValues).map { Returns(it) }) +} + +/** + * Sets consecutive return values to be returned when the method is called. + */ +fun OngoingStubbing.doReturnConsecutively(vararg values: T): OngoingStubbing { + return doReturnConsecutively(listOf(*values)) } /** * Sets consecutive return values to be returned when the method is called. */ -inline infix fun OngoingStubbing.doReturnConsecutively(ts: List): OngoingStubbing { - return thenReturn( - ts[0], - *ts.drop(1).toTypedArray() - ) +infix fun OngoingStubbing.doReturnConsecutively(values: List): OngoingStubbing { + return doAnswerInternal(values.map { Returns(it) }) } /** @@ -87,8 +111,8 @@ inline infix fun OngoingStubbing.doReturnConsecutively(ts: List OngoingStubbing.doThrow(t: Throwable): OngoingStubbing { - return thenThrow(t) +infix fun OngoingStubbing.doThrow(throwable: Throwable): OngoingStubbing { + return doAnswerInternal(ThrowsException(throwable)) } /** @@ -96,28 +120,39 @@ infix fun OngoingStubbing.doThrow(t: Throwable): OngoingStubbing { * * Alias for [OngoingStubbing.doThrow]. */ -fun OngoingStubbing.doThrow( - t: Throwable, - vararg ts: Throwable -): OngoingStubbing { - return thenThrow(t, *ts) +fun OngoingStubbing.doThrow(throwable: Throwable, vararg otherThrowables: Throwable): OngoingStubbing { + return doAnswerInternal(listOf(throwable, *otherThrowables).map { ThrowsException(it) }) } /** * Sets a Throwable type to be thrown when the method is called. */ -infix fun OngoingStubbing.doThrow(t: KClass): OngoingStubbing { - return thenThrow(t.java) +infix fun OngoingStubbing.doThrow(clazz: KClass): OngoingStubbing { + return doAnswerInternal(ThrowsExceptionForClassType(clazz.java)) } /** * Sets Throwable classes to be thrown when the method is called. */ fun OngoingStubbing.doThrow( - t: KClass, - vararg ts: KClass + clazz: KClass, + vararg otherClasses: KClass ): OngoingStubbing { - return thenThrow(t.java, *ts.map { it.java }.toTypedArray()) + return doAnswerInternal(listOf(clazz, *otherClasses).map { ThrowsExceptionForClassType(it.java) }) +} + +/** + * Calls the real method of the spy/mock when the method is called. + */ +fun OngoingStubbing.doCallRealMethod(): OngoingStubbing { + return callRealMethod() +} + +/** + * Calls the real method of the spy/mock when the method is called. + */ +fun OngoingStubbing.callRealMethod(): OngoingStubbing { + return doAnswerInternal(CallsRealMethods()) } /** @@ -126,16 +161,29 @@ fun OngoingStubbing.doThrow( * Alias for [OngoingStubbing.thenAnswer]. */ infix fun OngoingStubbing.doAnswer(answer: Answer<*>): OngoingStubbing { - return thenAnswer(answer) + return doAnswerInternal(answer) } /** * Sets a generic Answer for the method using a lambda. */ infix fun OngoingStubbing.doAnswer(answer: (KInvocationOnMock) -> T?): OngoingStubbing { - return thenAnswer(KAnswer(answer)) + return doAnswerInternal(KAnswer(answer)) } +/** + * Sets a generic Answer for a suspend function using a suspend lambda. + */ infix fun OngoingStubbing.doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): OngoingStubbing { - return thenAnswer(SuspendableAnswer(answer)) + return thenAnswer(CoroutineAwareAnswer(answer)) +} + +private fun OngoingStubbing.doAnswerInternal(vararg answers: Answer<*>): OngoingStubbing { + return doAnswerInternal(listOf(*answers)) +} + +private fun OngoingStubbing.doAnswerInternal(answers: List>): OngoingStubbing { + return answers.fold(this){ ongoingStubbing, answer -> + ongoingStubbing.thenAnswer(answer.wrapAsCoroutineAwareAnswer()) + } } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt index 9b521de4..b807beb0 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt @@ -27,51 +27,65 @@ package org.mockito.kotlin import kotlinx.coroutines.runBlocking import org.mockito.Mockito +import org.mockito.internal.stubbing.answers.CallsRealMethods +import org.mockito.internal.stubbing.answers.DoesNothing +import org.mockito.internal.stubbing.answers.Returns +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.internal.stubbing.answers.ThrowsExceptionForClassType import org.mockito.invocation.InvocationOnMock -import org.mockito.kotlin.internal.SuspendableAnswer -import org.mockito.stubbing.OngoingStubbing +import org.mockito.kotlin.internal.CoroutineAwareAnswer +import org.mockito.kotlin.internal.CoroutineAwareAnswer.Companion.wrapAsCoroutineAwareAnswer +import org.mockito.stubbing.Answer import org.mockito.stubbing.Stubber import kotlin.reflect.KClass -fun doAnswer(answer: (InvocationOnMock) -> T?): Stubber { - return Mockito.doAnswer { answer(it) }!! +fun doAnswer(answer: (InvocationOnMock) -> T): Stubber { + return doAnswerInternal(Answer { invocation -> answer(invocation) }) } fun doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): Stubber { - return Mockito.doAnswer(SuspendableAnswer(answer)) + return doAnswerInternal(CoroutineAwareAnswer(answer)) } fun doCallRealMethod(): Stubber { - return Mockito.doCallRealMethod()!! + return doAnswerInternal(CallsRealMethods()) } fun doNothing(): Stubber { - return Mockito.doNothing()!! + return doAnswerInternal(DoesNothing.doesNothing()) } fun doReturn(value: Any?): Stubber { - return Mockito.doReturn(value)!! + return doAnswerInternal(Returns(value)) } -fun doReturn(toBeReturned: Any?, vararg toBeReturnedNext: Any?): Stubber { - return Mockito.doReturn( - toBeReturned, - *toBeReturnedNext - )!! +fun doReturn(value: Any?, vararg otherValues: Any?): Stubber { + return doAnswerInternal( + listOf(value, *otherValues).map { Returns(it) } + ) } -fun doThrow(toBeThrown: KClass): Stubber { - return Mockito.doThrow(toBeThrown.java)!! +fun doThrow(clazz: KClass): Stubber { + return doAnswerInternal(ThrowsExceptionForClassType(clazz.java)) } -fun doThrow(vararg toBeThrown: Throwable): Stubber { - return Mockito.doThrow(*toBeThrown)!! +fun doThrow(vararg throwables: Throwable): Stubber { + return doAnswerInternal(listOf(*throwables).map { ThrowsException(it) }) } -fun Stubber.whenever(mock: T) = `when`(mock) +fun Stubber.whenever(mock: T): T = `when`(mock) /** - * Alias for when with suspending function + * Reverse stubber for suspending functions. + * + * Warning: Only one method call can be stubbed in the function. + * Subsequent method calls are ignored! + */ +fun Stubber.onBlocking(mock: T, f: suspend T.() -> Unit) = + wheneverBlocking(mock, f) + +/** + * Reverse stubber for suspending functions. * * Warning: Only one method call can be stubbed in the function. * Subsequent method calls are ignored! @@ -80,3 +94,20 @@ fun Stubber.wheneverBlocking(mock: T, f: suspend T.() -> Unit) { val m = whenever(mock) runBlocking { m.f() } } + +private fun doAnswerInternal(vararg answers: Answer<*>): Stubber { + return doAnswerInternal(listOf(*answers)) +} + +private fun doAnswerInternal(answers: List>): Stubber { + return answers + .map { it.wrapAsCoroutineAwareAnswer() } + .let { wrappedAnswers -> + val stubber = Mockito.doAnswer(wrappedAnswers.first()) + wrappedAnswers + .drop(1) + .fold(stubber) { stubber, answer -> + stubber.doAnswer(answer) + } + } +} diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/CoroutineAwareAnswer.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/CoroutineAwareAnswer.kt new file mode 100644 index 00000000..fad2232c --- /dev/null +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/CoroutineAwareAnswer.kt @@ -0,0 +1,124 @@ +/* + * 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.delay +import org.mockito.internal.invocation.InterceptedInvocation +import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.KInvocationOnMock +import org.mockito.stubbing.Answer +import java.lang.reflect.Method +import kotlin.coroutines.Continuation +import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.jvm.jvmErasure +import kotlin.reflect.jvm.kotlinFunction + +internal class CoroutineAwareAnswer private constructor(private val delegate: Answer) : Answer { + constructor(block: suspend (KInvocationOnMock) -> T?) : + this(SuspendableAnswer(block)) + + companion object { + internal fun Answer.wrapAsCoroutineAwareAnswer(): CoroutineAwareAnswer { + return this as? CoroutineAwareAnswer ?: CoroutineAwareAnswer(this) + } + } + + @Suppress("UNCHECKED_CAST") + override fun answer(invocation: InvocationOnMock): T { + val wrapped = if (invocation.isInvocationOnSuspendFunction ?: false) { + delegate.wrapAsSuspendableAnswer() + } else { + delegate + } + return wrapped.answer(invocation) as T + } + + private val InvocationOnMock.isInvocationOnSuspendFunction: Boolean? + get() { + return this.method.kotlinFunction?.isSuspend + } + + private fun Answer<*>.wrapAsSuspendableAnswer(): Answer = + (this as? SuspendableAnswer<*>) ?: + SuspendableAnswer( + { invocation -> + suspendToEnforceProperValueBoxing() + val result = this.answer(invocation) + result.boxAsKotlinType(invocation) + } + ) + + private fun Any.boxAsKotlinType(invocation: KInvocationOnMock): Any { + val returnType: KType = invocation.method.kotlinFunction?.returnType ?: return this + val returnCls: KClass<*> = returnType.jvmErasure + + if (this::class == returnCls) { + return this + } + + return if (returnCls.isValue) { + returnCls.boxAsValueType(this) + } else { + this + } + } + + private fun KClass<*>.boxAsValueType(value: Any): Any { + val boxImpl: Method = + java + .declaredMethods + .single { it.name == "box-impl" && it.parameterCount == 1 } + + return boxImpl.invoke(null, value) + } + + private suspend fun suspendToEnforceProperValueBoxing() { + // delaying for 1 ms, forces a suspension to happen. + // This (somehow) ensures that value class instances will be properly boxed when the + // answer is yielded by the mock + delay(1) + } + + /** + * This class properly wraps a suspendable lambda into a Mockito [Answer]. + */ + @Suppress("UNCHECKED_CAST") + private class SuspendableAnswer( + private val body: suspend (KInvocationOnMock) -> T? + ) : Answer { + override fun answer(invocation: InvocationOnMock?): T { + //all suspend functions/lambdas has Continuation as the last argument. + //InvocationOnMock does not see last argument + val rawInvocation = invocation as InterceptedInvocation + val continuation = rawInvocation.rawArguments.last() as Continuation + + // https://youtrack.jetbrains.com/issue/KT-33766#focus=Comments-27-3707299.0-0 + return body.startCoroutineUninterceptedOrReturn(KInvocationOnMock(invocation), continuation) as T + } + } +} diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt index ca62f048..a06728bf 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt @@ -34,9 +34,9 @@ import org.mockito.stubbing.Answer */ @Suppress("UNCHECKED_CAST") internal class KAnswer( - private val body: (KInvocationOnMock) -> T? + private val block: (KInvocationOnMock) -> T? ) : Answer { override fun answer(invocation: InvocationOnMock): T { - return body(KInvocationOnMock(invocation)) as T + return block.invoke(KInvocationOnMock(invocation)) as T } } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt deleted file mode 100644 index 239be2cd..00000000 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 org.mockito.internal.invocation.InterceptedInvocation -import org.mockito.invocation.InvocationOnMock -import org.mockito.kotlin.KInvocationOnMock -import org.mockito.stubbing.Answer -import kotlin.coroutines.Continuation -import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn - -/** - * This class properly wraps suspendable lambda into [Answer] - */ -@Suppress("UNCHECKED_CAST") -internal class SuspendableAnswer( - private val body: suspend (KInvocationOnMock) -> T? -) : Answer { - override fun answer(invocation: InvocationOnMock?): T { - //all suspend functions/lambdas has Continuation as the last argument. - //InvocationOnMock does not see last argument - val rawInvocation = invocation as InterceptedInvocation - val continuation = rawInvocation.rawArguments.last() as Continuation - - // https://youtrack.jetbrains.com/issue/KT-33766#focus=Comments-27-3707299.0-0 - return body.startCoroutineUninterceptedOrReturn(KInvocationOnMock(invocation), continuation) as T - } -} diff --git a/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt b/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt index b745022f..cce60d80 100644 --- a/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt +++ b/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt @@ -27,7 +27,7 @@ class BDDMockitoKtTest { val fixture: SomeInterface = mock() given(fixture.suspendingWithArg(any())).willSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } + withContext(Dispatchers.Default) { it.getArgument(0) } } assertEquals(42, fixture.suspendingWithArg(42)) @@ -36,12 +36,10 @@ class BDDMockitoKtTest { } @Test - fun willSuspendableAnswer_givenBlocking() { + fun willAnswer_givenBlocking_suspendLambdaAnswer() { val fixture: SomeInterface = mock() - givenBlocking { fixture.suspending() }.willSuspendableAnswer { - withContext(Dispatchers.Default) { 42 } - } + givenBlocking { fixture.suspending() }.willSuspendableAnswer { 42 } val result = runBlocking { fixture.suspending() @@ -49,7 +47,6 @@ class BDDMockitoKtTest { assertEquals(42, result) then(fixture).shouldBlocking { suspending() } - Unit } @Test @@ -57,7 +54,7 @@ class BDDMockitoKtTest { val fixture: SomeInterface = mock() givenBlocking { fixture.suspendingWithArg(any()) }.willSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } + withContext(Dispatchers.Default) { it.getArgument(0) } } val result = runBlocking { @@ -66,7 +63,6 @@ class BDDMockitoKtTest { assertEquals(42, result) then(fixture).shouldBlocking { suspendingWithArg(42) } - Unit } @Test diff --git a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt deleted file mode 100644 index fa5b9178..00000000 --- a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt +++ /dev/null @@ -1,537 +0,0 @@ -@file:Suppress("EXPERIMENTAL_FEATURE_WARNING") - -package test - -import com.nhaarman.expect.expect -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.* - -class CoroutinesTest { - - @Test - fun stubbingSuspending() { - /* Given */ - val m = mock { - onBlocking { suspending() } doReturn 42 - } - - /* When */ - val result = runBlocking { m.suspending() } - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun stubbingSuspending_usingSuspendingFunction() { - /* Given */ - val m = mock { - onBlocking { suspending() } doReturn runBlocking { SomeClass().result(42) } - } - - /* When */ - val result = runBlocking { m.suspending() } - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun stubbingSuspending_runBlocking() = runBlocking { - /* Given */ - val m = mock { - onBlocking { suspending() } doReturn 42 - } - - /* When */ - val result = m.suspending() - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun stubbingSuspending_wheneverBlocking() { - /* Given */ - val m: SomeInterface = mock() - wheneverBlocking { m.suspending() } - .doReturn(42) - - /* When */ - val result = runBlocking { m.suspending() } - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun stubbingSuspending_doReturn() { - /* Given */ - val m = spy(SomeClass()) - doReturn(10) - .wheneverBlocking(m) { - delaying() - } - - /* When */ - val result = runBlocking { m.delaying() } - - /* Then */ - expect(result).toBe(10) - } - - @Test - fun stubbingNonSuspending() { - /* Given */ - val m = mock { - onBlocking { nonsuspending() } doReturn 42 - } - - /* When */ - val result = m.nonsuspending() - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun stubbingNonSuspending_runBlocking() = runBlocking { - /* Given */ - val m = mock { - onBlocking { nonsuspending() } doReturn 42 - } - - /* When */ - val result = m.nonsuspending() - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun delayingResult() { - /* Given */ - val m = SomeClass() - - /* When */ - val result = runBlocking { m.delaying() } - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun delayingResult_runBlocking() = runBlocking { - /* Given */ - val m = SomeClass() - - /* When */ - val result = m.delaying() - - /* Then */ - expect(result).toBe(42) - } - - @Test - fun verifySuspendFunctionCalled() { - /* Given */ - val m = mock() - - /* When */ - runBlocking { m.suspending() } - - /* Then */ - runBlocking { verify(m).suspending() } - } - - @Test - fun verifySuspendFunctionCalled_runBlocking() = runBlocking { - val m = mock() - - m.suspending() - - verify(m).suspending() - } - - @Test - fun verifySuspendFunctionCalled_verifyBlocking() { - val m = mock() - - runBlocking { m.suspending() } - - verifyBlocking(m) { suspending() } - } - - @Test - fun verifyAtLeastOnceSuspendFunctionCalled_verifyBlocking() { - val m = mock() - - runBlocking { m.suspending() } - runBlocking { m.suspending() } - - verifyBlocking(m, atLeastOnce()) { suspending() } - } - - @Test - fun verifySuspendMethod() = runBlocking { - val testSubject: SomeInterface = mock() - - testSubject.suspending() - - inOrder(testSubject) { - verify(testSubject).suspending() - } - } - - @Test - fun answerWithSuspendFunction() = runBlocking { - val fixture: SomeInterface = mock() - - whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - } - - assertEquals(5, fixture.suspendingWithArg(5)) - } - - @Test - fun inplaceAnswerWithSuspendFunction() = runBlocking { - val fixture: SomeInterface = mock { - onBlocking { suspendingWithArg(any()) } doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - } - } - - assertEquals(5, fixture.suspendingWithArg(5)) - } - - @Test - fun callFromSuspendFunction() = runBlocking { - val fixture: SomeInterface = mock() - - whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - } - - val result = async { - val answer = fixture.suspendingWithArg(5) - - Result.success(answer) - } - - assertEquals(5, result.await().getOrThrow()) - } - - @Test - @OptIn(ObsoleteCoroutinesApi::class) - fun callFromActor() = runBlocking { - val fixture: SomeInterface = mock() - - whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - } - - val actor = actor> { - for (element in channel) { - fixture.suspendingWithArg(element.get()) - } - } - - actor.send(Optional.of(10)) - actor.close() - - verify(fixture).suspendingWithArg(10) - - Unit - } - - @Test - fun answerWithSuspendFunctionWithoutArgs() = runBlocking { - val fixture: SomeInterface = mock() - - whenever(fixture.suspending()).doSuspendableAnswer { - withContext(Dispatchers.Default) { 42 } - } - - assertEquals(42, fixture.suspending()) - } - - @Test - fun answerWithSuspendFunctionWithDestructuredArgs() = runBlocking { - val fixture: SomeInterface = mock() - - whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer { (i: Int) -> - withContext(Dispatchers.Default) { i } - } - - assertEquals(5, fixture.suspendingWithArg(5)) - } - - @Test - fun willAnswerWithControlledSuspend() = runBlocking { - val fixture: SomeInterface = mock() - - val job = Job() - - whenever(fixture.suspending()).doSuspendableAnswer { - job.join() - 5 - } - - val asyncTask = async { - fixture.suspending() - } - - job.complete() - - withTimeout(100) { - assertEquals(5, asyncTask.await()) - } - } - - @Test - fun stubberAnswerWithSuspendFunction() = runBlocking { - val fixture: SomeInterface = mock() - - doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - }.whenever(fixture).suspendingWithArg(any()) - - assertEquals(5, fixture.suspendingWithArg(5)) - } - - @Test - fun stubberCallFromSuspendFunction() = runBlocking { - val fixture: SomeInterface = mock() - - doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - }.whenever(fixture).suspendingWithArg(any()) - - val result = async { - val answer = fixture.suspendingWithArg(5) - - Result.success(answer) - } - - assertEquals(5, result.await().getOrThrow()) - } - - @Test - @OptIn(ObsoleteCoroutinesApi::class) - fun stubberCallFromActor() = runBlocking { - val fixture: SomeInterface = mock() - - doSuspendableAnswer { - withContext(Dispatchers.Default) { it.getArgument(0) } - }.whenever(fixture).suspendingWithArg(any()) - - val actor = actor> { - for (element in channel) { - fixture.suspendingWithArg(element.get()) - } - } - - actor.send(Optional.of(10)) - actor.close() - - verify(fixture).suspendingWithArg(10) - - Unit - } - - @Test - fun stubberAnswerWithSuspendFunctionWithoutArgs() = runBlocking { - val fixture: SomeInterface = mock() - - doSuspendableAnswer { - withContext(Dispatchers.Default) { 42 } - }.whenever(fixture).suspending() - - assertEquals(42, fixture.suspending()) - } - - @Test - fun stubberAnswerWithSuspendFunctionWithDestructuredArgs() = runBlocking { - val fixture: SomeInterface = mock() - - doSuspendableAnswer { (i: Int) -> - withContext(Dispatchers.Default) { i } - }.whenever(fixture).suspendingWithArg(any()) - - assertEquals(5, fixture.suspendingWithArg(5)) - } - - @Test - fun stubberWillAnswerWithControlledSuspend() = runBlocking { - val fixture: SomeInterface = mock() - - val job = Job() - - doSuspendableAnswer { - job.join() - 5 - }.whenever(fixture).suspending() - - val asyncTask = async { - fixture.suspending() - } - - job.complete() - - withTimeout(100) { - assertEquals(5, asyncTask.await()) - } - } - - @Test - fun inOrderRemainsCompatible() { - /* Given */ - val fixture: SomeInterface = mock() - - /* When */ - val inOrder = inOrder(fixture) - - /* Then */ - expect(inOrder).toBeInstanceOf() - } - - @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 { - - suspend fun suspending(): Int - suspend fun suspendingWithArg(arg: Int): Int - fun nonsuspending(): Int -} - -open class SomeClass { - - suspend fun result(r: Int) = withContext(Dispatchers.Default) { r } - - open suspend fun delaying() = withContext(Dispatchers.Default) { - delay(100) - 42 - } -} diff --git a/tests/build.gradle b/tests/build.gradle index dd447d93..6bb524c8 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -19,7 +19,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation "com.nhaarman:expect.kt:1.0.1" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0-RC" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2" } tasks.withType(KotlinCompile).configureEach { diff --git a/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt b/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt new file mode 100644 index 00000000..5fc20e3d --- /dev/null +++ b/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt @@ -0,0 +1,141 @@ +package org.mockito.kotlin.internal + +import com.nhaarman.expect.expect +import org.junit.Test +import org.mockito.kotlin.anyVararg +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.mock +import test.SynchronousFunctions +import test.assertThrows + +class KAnswerTest { + @Test + fun `should answer with first invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.first() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should answer with second invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.second() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("B") + } + + @Test + fun `should answer with third invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.third() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("C") + } + + @Test + fun `should answer with fourth invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.fourth() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("D") + } + + @Test + fun `should answer with fifth invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.fifth() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("E") + } + + @Test + fun `should answer with last invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.last() } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("E") + } + + @Test + fun `should answer with all invocation arguments`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { + it.all().joinToString("; ") + } + } + + /* When */ + val result = mock.varargStringResult("A", "B", "C", "D", "E") + + /* Then */ + expect(result).toBe("A; B; C; D; E") + } + + @Test + fun `should answer with single invocation argument`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.single() } + } + + /* When */ + val result = mock.varargStringResult("A") + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should throw when trying to answer with single invocation argument, but with actually more`() { + /* Given */ + val mock = mock { + on { varargStringResult(anyVararg()) } doAnswer { it.single() } + } + + /* When, Then */ + val exception: IllegalArgumentException = assertThrows { + mock.varargStringResult("A", "B") + } + + /* Then */ + expect(exception.message).toContain("xpected to have exactly 1 argument but got 2") + } +} diff --git a/tests/src/test/kotlin/test/AdditionalMatchersTest.kt b/tests/src/test/kotlin/test/AdditionalMatchersTest.kt index bb6f5ede..9e42ed64 100644 --- a/tests/src/test/kotlin/test/AdditionalMatchersTest.kt +++ b/tests/src/test/kotlin/test/AdditionalMatchersTest.kt @@ -7,7 +7,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testGeq() { - mock().apply { + mock().apply { int(1) verify(this).int(geq(0)) verify(this).int(geq(1)) @@ -17,7 +17,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testLeq() { - mock().apply { + mock().apply { int(1) verify(this).int(leq(2)) verify(this).int(leq(1)) @@ -27,7 +27,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testGt() { - mock().apply { + mock().apply { int(1) verify(this).int(gt(0)) verify(this, never()).int(gt(1)) @@ -36,7 +36,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testLt() { - mock().apply { + mock().apply { int(1) verify(this).int(lt(2)) verify(this, never()).int(lt(1)) @@ -45,7 +45,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testCmpEq() { - mock().apply { + mock().apply { int(1) verify(this).int(cmpEq(1)) verify(this, never()).int(cmpEq(2)) @@ -54,7 +54,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqBoolean() { - mock().apply { + mock().apply { booleanArray(booleanArrayOf(true, false, true)) verify(this).booleanArray(aryEq(booleanArrayOf(true, false, true))) verify(this, never()).booleanArray(aryEq(booleanArrayOf(true, false))) @@ -63,7 +63,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqByte() { - mock().apply { + mock().apply { byteArray(byteArrayOf(1, 2, 3)) verify(this).byteArray(aryEq(byteArrayOf(1, 2, 3))) verify(this, never()).byteArray(aryEq(byteArrayOf(1, 2))) @@ -72,7 +72,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqShort() { - mock().apply { + mock().apply { shortArray(shortArrayOf(1, 2, 3)) verify(this).shortArray(aryEq(shortArrayOf(1, 2, 3))) verify(this, never()).shortArray(aryEq(shortArrayOf(1, 2))) @@ -81,7 +81,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqInt() { - mock().apply { + mock().apply { intArray(intArrayOf(1, 2, 3)) verify(this).intArray(aryEq(intArrayOf(1, 2, 3))) verify(this, never()).intArray(aryEq(intArrayOf(1, 2))) @@ -90,7 +90,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqLong() { - mock().apply { + mock().apply { longArray(longArrayOf(1, 2, 3)) verify(this).longArray(aryEq(longArrayOf(1, 2, 3))) verify(this, never()).longArray(aryEq(longArrayOf(1, 2))) @@ -99,7 +99,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqChar() { - mock().apply { + mock().apply { charArray(charArrayOf('1', '2', '3')) verify(this).charArray(aryEq(charArrayOf('1', '2', '3'))) verify(this, never()).charArray(aryEq(charArrayOf('1', '2'))) @@ -108,7 +108,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqFloat() { - mock().apply { + mock().apply { floatArray(floatArrayOf(1f, 2f, 3.4f)) verify(this).floatArray(aryEq(floatArrayOf(1f, 2f, 3.4f))) verify(this, never()).floatArray(aryEq(floatArrayOf(1f, 2f))) @@ -117,7 +117,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEqDouble() { - mock().apply { + mock().apply { doubleArray(doubleArrayOf(1.0, 2.0, 3.4)) verify(this).doubleArray(aryEq(doubleArrayOf(1.0, 2.0, 3.4))) verify(this, never()).doubleArray(aryEq(doubleArrayOf(1.0, 2.0))) @@ -126,7 +126,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAryEq() { - mock().apply { + mock().apply { stringArray(arrayOf("Hello", "there")) verify(this).stringArray(aryEq(arrayOf("Hello", "there"))) verify(this, never()).stringArray(aryEq(arrayOf("Hello"))) @@ -135,7 +135,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testFind() { - mock().apply { + mock().apply { string("Hello") verify(this).string(find("l+o$".toRegex())) verify(this, never()).string(find("l$".toRegex())) @@ -144,7 +144,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testAnd() { - mock().apply { + mock().apply { int(5) verify(this).int(and(geq(4), leq(6))) verify(this, never()).int(and(geq(4), leq(4))) @@ -153,7 +153,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testOr() { - mock().apply { + mock().apply { int(5) verify(this).int(and(gt(4), lt(6))) verify(this, never()).int(and(gt(4), lt(4))) @@ -162,7 +162,7 @@ class AdditionalCaptorsTest : TestBase() { @Test fun testNot() { - mock().apply { + mock().apply { int(5) verify(this).int(not(eq(4))) verify(this, never()).int(not(eq(5))) diff --git a/tests/src/test/kotlin/test/ArgumentCaptorTest.kt b/tests/src/test/kotlin/test/ArgumentCaptorTest.kt index 14382a5c..891f7ff5 100644 --- a/tests/src/test/kotlin/test/ArgumentCaptorTest.kt +++ b/tests/src/test/kotlin/test/ArgumentCaptorTest.kt @@ -101,7 +101,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_withNullValue_usingNonNullable() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.nullableString(null) @@ -115,7 +115,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_withNullValue_usingNullable() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.nullableString(null) @@ -173,7 +173,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_multipleValuesIncludingNull() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.nullableString("test") @@ -188,7 +188,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_callProperties() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.int(1) @@ -211,7 +211,7 @@ class ArgumentCaptorTest : TestBase() { @Test(expected = IndexOutOfBoundsException::class) fun argumentCaptor_callPropertyNotAvailable() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.int(1) @@ -259,7 +259,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_vararg() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.varargBooleanResult("a", "b", "c") @@ -273,7 +273,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_empty_vararg() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.varargBooleanResult() @@ -287,7 +287,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_arg_vararg() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.argAndVararg("first", "a", "b", "c") @@ -301,7 +301,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_intarray() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.intArray(intArrayOf(1, 2, 3)) @@ -315,7 +315,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_array() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.stringArray(arrayOf("a", "b", "c")) @@ -329,7 +329,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_empty_array() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.stringArray(arrayOf()) @@ -343,7 +343,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_value_class() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() val valueClass = ValueClass("Content") /* When */ @@ -358,7 +358,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_value_class_withNullValue_usingNonNullable() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.nullableValueClass(null) @@ -372,7 +372,7 @@ class ArgumentCaptorTest : TestBase() { @Test fun argumentCaptor_value_class_withNullValue_usingNullable() { /* Given */ - val m: Methods = mock() + val m: SynchronousFunctions = mock() /* When */ m.nullableValueClass(null) diff --git a/tests/src/test/kotlin/test/BDDMockitoTest.kt b/tests/src/test/kotlin/test/BDDMockitoTest.kt index 0472163f..457bf406 100644 --- a/tests/src/test/kotlin/test/BDDMockitoTest.kt +++ b/tests/src/test/kotlin/test/BDDMockitoTest.kt @@ -10,7 +10,7 @@ class BDDMockitoTest { @Test fun given_will_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()) will Answer { "Test" } @@ -22,7 +22,7 @@ class BDDMockitoTest { @Test fun given_willReturn_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()).willReturn("Test") @@ -34,7 +34,7 @@ class BDDMockitoTest { @Test fun givenLambda_willReturn_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given { mock.stringResult() }.willReturn("Test") @@ -46,7 +46,7 @@ class BDDMockitoTest { @Test fun given_willReturnLambda_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()).willReturn { "Test" } @@ -58,7 +58,7 @@ class BDDMockitoTest { @Test fun givenLambda_willReturnLambda_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given { mock.stringResult() } willReturn { "Test" } @@ -70,7 +70,7 @@ class BDDMockitoTest { @Test fun given_willAnswer_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()).willAnswer { "Test" } @@ -82,7 +82,7 @@ class BDDMockitoTest { @Test fun given_willAnswerInfix_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()) willAnswer { "Test" } @@ -94,7 +94,7 @@ class BDDMockitoTest { @Test fun given_willAnswerInfix_withInvocationInfo_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult(any())) willAnswer { invocation -> @@ -109,7 +109,7 @@ class BDDMockitoTest { @Test(expected = IllegalStateException::class) fun given_willThrowInfix_properlyStubs() { /* Given */ - val mock = mock() + val mock = mock() /* When */ given(mock.stringResult()) willThrow { IllegalStateException() } @@ -119,13 +119,13 @@ class BDDMockitoTest { @Test fun then() { /* Given */ - val mock = mock() + val mock = mock() whenever(mock.stringResult()).thenReturn("Test") /* When */ mock.stringResult() /* Then */ - org.mockito.kotlin.then(mock).should().stringResult() + then(mock).should().stringResult() } } diff --git a/tests/src/test/kotlin/test/Classes.kt b/tests/src/test/kotlin/test/Classes.kt index a7a92703..8cfb56af 100644 --- a/tests/src/test/kotlin/test/Classes.kt +++ b/tests/src/test/kotlin/test/Classes.kt @@ -37,16 +37,17 @@ open class Open { open fun stringResult() = "Default" + fun valueClassResult(arg: ValueClass): ValueClass = ValueClass("Result: ${arg.content}") + suspend fun suspendValueClassResult(arg: suspend () -> ValueClass): ValueClass = ValueClass("Result: ${arg().content}") + fun throwsNPE(): Any = throw NullPointerException("Test") } class Closed -interface Methods { - +interface SynchronousFunctions { fun closed(c: Closed) fun classClosed(c: Class) - suspend fun coroutinesClosed(c: Closed) fun closedArray(a: Array) fun closedNullableArray(a: Array) fun closedCollection(c: Collection) @@ -77,10 +78,9 @@ interface Methods { fun stringResult(): String fun stringResult(s: String): String fun nullableStringResult(): String? - fun builderMethod(): Methods + fun builderMethod(): SynchronousFunctions fun varargBooleanResult(vararg values: String): Boolean - suspend fun coroutinesClosedBooleanResult(c: Closed): Boolean - suspend fun coroutinesClassClosedBooleanResult(c: Class): Boolean + fun varargStringResult(vararg values: String): String fun stringArray(a: Array) fun argAndVararg(s: String, vararg a: String) @@ -89,10 +89,27 @@ interface Methods { fun valueClass(v: ValueClass) fun nullableValueClass(v: ValueClass?) fun nestedValueClass(v: NestedValueClass) + fun valueClassResult(): ValueClass + fun nullableValueClassResult(): ValueClass? + fun nestedValueClassResult(): NestedValueClass +} + +interface SuspendFunctions { + suspend fun closed(c: Closed) + suspend fun closedBooleanResult(c: Closed): Boolean + suspend fun classClosedBooleanResult(c: Class): Boolean + suspend fun stringResult(): String + suspend fun stringResult(s: String): String + suspend fun stringResult(s1: String, s2: String): String + suspend fun nullableStringResult(): String? + suspend fun valueClassResult(): ValueClass + suspend fun nullableValueClassResult(): ValueClass? + suspend fun nestedValueClassResult(): NestedValueClass + suspend fun builderMethod(): SuspendFunctions } @JvmInline -value class ValueClass(private val content: String) +value class ValueClass(val content: String) @JvmInline value class NestedValueClass(val value: ValueClass) diff --git a/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt new file mode 100644 index 00000000..db965cc0 --- /dev/null +++ b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt @@ -0,0 +1,365 @@ +package test + +import com.nhaarman.expect.expect +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Ignore +import org.junit.Test +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doCallRealMethod +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doReturnConsecutively +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.stubbing.Answer + +class CoroutinesOngoingStubbingTest { + @Test + fun `should stub suspendable function call`() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "A" + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call within a coroutine scope`() = runTest { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "A" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub consecutive suspendable function calls`() { + /* Given */ + val mock = mock { + onBlocking { stringResult() }.doReturn("A", "B", "C") + } + + /* When */ + val result = runBlocking { + (1..3).map { _ -> + mock.stringResult() + } + } + + /* Then */ + expect(result).toBe(listOf("A", "B", "C")) + } + + @Test + fun `should stub consecutive suspendable function calls by a list of answers`() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturnConsecutively listOf("A", "B", "C") + } + + /* When */ + val result = runBlocking { + (1..3).map { _ -> + mock.stringResult() + } + } + + /* Then */ + expect(result).toBe(listOf("A", "B", "C")) + } + + @Ignore("Default answers do not yet work for coroutines, see https://github.com/mockito/mockito-kotlin/issues/550") + @Test + fun `should stub builder method returning mock itself via defaultAnswer`() { + /* Given */ + val mock = mock(defaultAnswer = Mockito.RETURNS_SELF) + + /* When */ + val result = runBlocking { mock.builderMethod() } + + /* Then */ + expect(result).toBeTheSameAs(mock) + } + + @Ignore("Default answers do not yet work for coroutines, see https://github.com/mockito/mockito-kotlin/issues/550") + @Test + fun `should stub builder method returning mock itself via answer`() { + /* Given */ + val mock = mock { + onBlocking { builderMethod() } doAnswer Mockito.RETURNS_SELF + } + + /* When */ + val result = runBlocking { mock.builderMethod() } + + /* Then */ + expect(result).toBeTheSameAs(mock) + } + + @Test + fun `should stub builder method returning mock itself`() { + /* Given */ + val mock = mock { mock -> + onBlocking { builderMethod() } doReturn mock + } + + /* When */ + val result = runBlocking { mock.builderMethod() } + + /* Then */ + expect(result).toBe(mock) + } + + @Test + fun `should stub suspendable function call with nullable result`() { + /* Given */ + val mock = mock { + onBlocking { nullableStringResult() } doReturn "Test" + } + + /* When */ + val result = runBlocking { mock.nullableStringResult() } + + /* Then */ + expect(result).toBe("Test") + } + + @Test + fun `should throw exception instance on suspendable function call`() { + /* Given */ + val mock = mock { + onBlocking { builderMethod() } doThrow IllegalArgumentException() + } + + /* When, Then */ + runBlocking { + assertThrows { + mock.builderMethod() + } + } + } + + @Test + fun `should throw exception class on suspendable function call`() { + /* Given */ + val mock = mock { + onBlocking { builderMethod() } doThrow IllegalArgumentException::class + } + + /* When, Then */ + runBlocking { + assertThrows { + mock.builderMethod() + } + } + } + + @Test + fun `should throw exception instances on consecutive suspendable function calls`() { + /* Given */ + val mock = mock { + onBlocking { builderMethod() }.doThrow( + IllegalArgumentException(), + UnsupportedOperationException() + ) + } + + /* When, Then */ + runBlocking { + assertThrows { + mock.builderMethod() + } + assertThrows { + mock.builderMethod() + } + } + } + + @Test + fun `should throw exception classes on consecutive suspendable function calls`() { + /* Given */ + val mock = mock { + onBlocking { builderMethod() }.doThrow( + IllegalArgumentException::class, + UnsupportedOperationException::class + ) + } + + /* When, Then */ + runBlocking { + assertThrows { + mock.builderMethod() + } + assertThrows { + mock.builderMethod() + } + } + } + + @Test + fun `should stub suspendable function call with result from answer instance`() { + /* Given */ + val answer: Answer = Answer { "result" } + val mock = mock { + onBlocking { stringResult() } doAnswer answer + } + + /* When */ + + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("result") + } + + @Test + fun `should stub suspendable function call with result from lambda`() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doAnswer { "result" } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("result") + } + + @Test + fun `should stub suspendable function call with result from lambda with argument`() { + /* Given */ + val mock = mock { + onBlocking { stringResult(any()) } doAnswer { "${it.arguments[0]}-result" } + } + + /* When */ + val result = runBlocking { mock.stringResult("argument") } + + /* Then */ + expect(result).toBe("argument-result") + } + + @Test + fun `should stub suspendable function call with result from lambda with deconstructed argument`() { + /* Given */ + val mock = mock { + onBlocking { stringResult(any()) } doAnswer { (s: String) -> "$s-result" } + } + + /* When */ + val result = runBlocking { mock.stringResult("argument") } + + /* Then */ + expect(result).toBe("argument-result") + } + + @Test + fun `should stub suspendable function call with result from lambda with deconstructed arguments`() { + /* Given */ + val mock = mock { + onBlocking { stringResult(any(), any()) } doAnswer { (a: String, b: String) -> + "$a + $b" + } + } + + /* When */ + val result = runBlocking { mock.stringResult("apple", "banana") } + + /* Then */ + expect(result).toBe("apple + banana") + } + + @Test + fun `should stub suspendable function call with value class result`() { + /* Given */ + val valueClass = ValueClass("A") + val mock = mock { + onBlocking { valueClassResult() } doReturn valueClass + } + + /* When */ + val result: ValueClass = runBlocking { mock.valueClassResult() } + + /* Then */ + expect(result).toBe(valueClass) + } + + @Test + fun `should stub suspendable function call with nullable value class result`() { + /* Given */ + val valueClass = ValueClass("A") + val mock = mock { + onBlocking { nullableValueClassResult() } doReturn valueClass + } + + /* When */ + val result: ValueClass? = runBlocking { mock.nullableValueClassResult() } + + /* Then */ + expect(result).toBe(valueClass) + } + + @Test + fun `should stub consecutive suspendable function call with value class results`() { + /* Given */ + val valueClassA = ValueClass("A") + val valueClassB = ValueClass("B") + val mock = mock { + onBlocking { valueClassResult() }.doReturnConsecutively(valueClassA, valueClassB) + } + + /* When */ + val (result1, result2) = runBlocking { + mock.valueClassResult() to mock.valueClassResult() + } + + /* Then */ + expect(result1).toBe(valueClassA) + expect(result2).toBe(valueClassB) + } + + @Test + fun `should stub suspendable function call with nested value class result`() { + /* Given */ + val nestedValueClass = NestedValueClass(ValueClass("A")) + val mock = mock { + onBlocking { nestedValueClassResult() } doReturn nestedValueClass + } + + /* When */ + val result: NestedValueClass = runBlocking { mock.nestedValueClassResult() } + + /* Then */ + expect(result).toBe(nestedValueClass) + expect(result.value).toBe(nestedValueClass.value) + } + + @Test + fun `should stub suspendable function call to make real function call into mock`() { + /* Given */ + val mock = mock { + onBlocking { suspendValueClassResult(any()) }.doCallRealMethod() + } + + /* When */ + val result: ValueClass = runBlocking { + mock.suspendValueClassResult { ValueClass("Value") } + } + + /* Then */ + expect(result.content).toBe("Result: Value") + } +} diff --git a/tests/src/test/kotlin/test/CoroutinesTest.kt b/tests/src/test/kotlin/test/CoroutinesTest.kt new file mode 100644 index 00000000..93f2e443 --- /dev/null +++ b/tests/src/test/kotlin/test/CoroutinesTest.kt @@ -0,0 +1,512 @@ +package test + +import com.nhaarman.expect.expect +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 org.mockito.kotlin.doReturn +import org.mockito.kotlin.inOrder +import java.util.* + +class CoroutinesTest { + + @Test + fun stubbingSuspending() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "Value" + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingSuspending_usingSuspendingFunction() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn runBlocking { SomeClass().result("Value") } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingSuspending_runBlocking() = runBlocking { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "Value" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingSuspending_wheneverBlocking() { + /* Given */ + val mock: SuspendFunctions = mock() + wheneverBlocking { mock.stringResult() } + .doReturn("Value") + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingSuspending_doReturn() { + /* Given */ + val mock = spy(SomeClass()) + doReturn("Value").wheneverBlocking(mock) { delaying() } + + /* When */ + val result = runBlocking { mock.delaying() } + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingNonSuspending() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "Value" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun stubbingNonSuspending_runBlocking() = runBlocking { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "Value" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun delayingResult() { + /* Given */ + val instance = SomeClass() + + /* When */ + val result = runBlocking { instance.delaying() } + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun delayingResult_runBlocking() = runBlocking { + /* Given */ + val instance = SomeClass() + + /* When */ + val result = instance.delaying() + + /* Then */ + expect(result).toBe("Value") + } + + @Test + fun verifySuspendFunctionCalled() { + /* Given */ + val mock = mock() + + /* When */ + runBlocking { mock.stringResult() } + + /* Then */ + runBlocking { verify(mock).stringResult() } + } + + @Test + fun verifySuspendFunctionCalled_runBlocking() = runBlocking { + val mock = mock() + + mock.stringResult() + + verify(mock).stringResult() + } + + @Test + fun verifySuspendFunctionCalled_verifyBlocking() { + val mock = mock() + + runBlocking { mock.stringResult() } + + verifyBlocking(mock) { stringResult() } + } + + @Test + fun verifyAtLeastOnceSuspendFunctionCalled_verifyBlocking() { + val mock = mock() + + runBlocking { mock.stringResult() } + runBlocking { mock.stringResult() } + + verifyBlocking(mock, atLeastOnce()) { stringResult() } + } + + @Test + fun verifySuspendMethod() = runBlocking { + val mock: SuspendFunctions = mock() + + mock.stringResult() + + inOrder(mock) { + verify(mock).stringResult() + } + } + + @Test + fun answerWithSuspendFunction() = runBlocking { + val mock: SuspendFunctions = mock() + + wheneverBlocking {mock.stringResult(any()) } doAnswer { it.single() } + + assertEquals("Value", mock.stringResult("Value")) + } + + @Test + fun inplaceAnswerWithSuspendFunction() = runBlocking { + val mock: SuspendFunctions = mock { + onBlocking { stringResult(any()) } doAnswer { it.single() } + } + + assertEquals("Value", mock.stringResult("Value")) + } + + @Test + fun callFromSuspendFunction() = runBlocking { + val mock: SuspendFunctions = mock() + + wheneverBlocking {mock.stringResult(any()) } doAnswer { it.single() } + + val result = async { + val answer = mock.stringResult("Value") + + Result.success(answer) + } + + assertEquals("Value", result.await().getOrThrow()) + } + + @Test + @OptIn(ObsoleteCoroutinesApi::class) + fun callFromActor() = runBlocking { + val mock: SuspendFunctions = mock() + + wheneverBlocking { mock.stringResult(any()) } doAnswer { it.single() } + + val actor = actor> { + for (element in channel) { + mock.stringResult(element.get()) + } + } + + actor.send(Optional.of("Value")) + actor.close() + + verify(mock).stringResult("Value") + + Unit + } + + @Test + fun answerWithSuspendFunctionWithoutArgs() = runBlocking { + val mock: SuspendFunctions = mock() + + wheneverBlocking { mock.stringResult() } doReturn "Value" + + assertEquals("Value", mock.stringResult()) + } + + @Test + fun answerWithSuspendFunctionWithDestructuredArgs() = runBlocking { + val mock: SuspendFunctions = mock() + + wheneverBlocking { mock.stringResult(any()) } doAnswer { (s: String) -> s } + + assertEquals("Value", mock.stringResult("Value")) + } + + @Test + fun willAnswerWithControlledSuspend() = runBlocking { + val mock: SuspendFunctions = mock() + + val job = Job() + + wheneverBlocking { mock.stringResult() } doSuspendableAnswer { + job.join() + "Value" + } + + val asyncTask = async { + mock.stringResult() + } + + job.complete() + + withTimeout(100) { + assertEquals("Value", asyncTask.await()) + } + } + + @Test + fun stubberAnswerWithSuspendFunction() = runBlocking { + val mock: SuspendFunctions = mock() + + doSuspendableAnswer { it.single() }.whenever(mock).stringResult(any()) + + assertEquals("Value", mock.stringResult("Value")) + } + + @Test + fun stubberCallFromSuspendFunction() = runBlocking { + val mock: SuspendFunctions = mock() + + doSuspendableAnswer { it.single() }.whenever(mock).stringResult(any()) + + val result = async { + val answer = mock.stringResult("Value") + + Result.success(answer) + } + + assertEquals("Value", result.await().getOrThrow()) + } + + @Test + @OptIn(ObsoleteCoroutinesApi::class) + fun stubberCallFromActor() { + val mock: SuspendFunctions = mock() + + doSuspendableAnswer { + withContext(Dispatchers.Default) { it.single() } + }.wheneverBlocking(mock) { stringResult(any()) } + + runBlocking { + val actor = actor> { + for (element in channel) { + mock.stringResult(element.get()) + } + } + + actor.send(Optional.of("Value")) + actor.close() + } + + verifyBlocking(mock) {stringResult("Value") } + } + + @Test + fun stubberAnswerWithSuspendFunctionWithoutArgs() { + val mock: SuspendFunctions = mock() + + doSuspendableAnswer { + withContext(Dispatchers.Default) { "Value" } + }.wheneverBlocking(mock) { stringResult() } + + assertEquals("Value", runBlocking { mock.stringResult() }) + } + + @Test + fun stubberAnswerWithSuspendFunctionWithDestructuredArgs() { + val mock: SuspendFunctions = mock() + + doSuspendableAnswer { (s: String) -> + withContext(Dispatchers.Default) { s } + }.wheneverBlocking(mock) { stringResult(any()) } + + val actual = runBlocking { mock.stringResult("Value") } + assertEquals("Value", actual) + } + + @Test + fun stubberWillAnswerWithControlledSuspend() { + val mock: SuspendFunctions = mock() + + val job = Job() + + doSuspendableAnswer { + job.join() + "Value" + }.wheneverBlocking(mock) { stringResult() } + + runBlocking { + val asyncTask = async { + mock.stringResult() + } + + job.complete() + + withTimeout(100) { + assertEquals("Value", asyncTask.await()) + } + } + } + + @Test + fun inOrderRemainsCompatible() { + /* Given */ + val mock: SuspendFunctions = mock() + + /* When */ + val inOrder = inOrder(mock) + + /* Then */ + expect(inOrder).toBeInstanceOf() + } + + @Test + fun inOrderSuspendingCalls() { + /* Given */ + val fixtureOne: SuspendFunctions = mock() + val fixtureTwo: SuspendFunctions = mock() + + /* When */ + runBlocking { + fixtureOne.stringResult() + fixtureTwo.stringResult() + } + + /* Then */ + val inOrder = inOrder(fixtureOne, fixtureTwo) + inOrder.verifyBlocking(fixtureOne) { stringResult() } + inOrder.verifyBlocking(fixtureTwo) { stringResult() } + } + + @Test + fun inOrderSuspendingCallsFailure() { + /* Given */ + val fixtureOne: SuspendFunctions = mock() + val fixtureTwo: SuspendFunctions = mock() + + /* When */ + runBlocking { + fixtureOne.stringResult() + fixtureTwo.stringResult() + } + + /* Then */ + val inOrder = inOrder(fixtureOne, fixtureTwo) + inOrder.verifyBlocking(fixtureTwo) { stringResult() } + assertThrows(AssertionError::class.java) { + inOrder.verifyBlocking(fixtureOne) { stringResult() } + } + } + + @Test + fun inOrderBlockSuspendingCalls() { + /* Given */ + val fixtureOne: SuspendFunctions = mock() + val fixtureTwo: SuspendFunctions = mock() + + /* When */ + runBlocking { + fixtureOne.stringResult() + fixtureTwo.stringResult() + } + + /* Then */ + inOrder(fixtureOne, fixtureTwo) { + verifyBlocking(fixtureOne) { stringResult() } + verifyBlocking(fixtureTwo) { stringResult() } + } + } + + @Test + fun inOrderBlockSuspendingCallsFailure() { + /* Given */ + val fixtureOne: SuspendFunctions = mock() + val fixtureTwo: SuspendFunctions = mock() + + /* When */ + runBlocking { + fixtureOne.stringResult() + fixtureTwo.stringResult() + } + + /* Then */ + inOrder(fixtureOne, fixtureTwo) { + verifyBlocking(fixtureTwo) { stringResult() } + assertThrows(AssertionError::class.java) { + verifyBlocking(fixtureOne) { stringResult() } + } + } + } + + @Test + fun inOrderOnObjectSuspendingCalls() { + /* Given */ + val mock: SuspendFunctions = mock() + + /* When */ + runBlocking { + mock.stringResult("Value") + mock.stringResult("Other Value") + } + + /* Then */ + mock.inOrder { + verifyBlocking { stringResult("Value") } + verifyBlocking { stringResult("Other Value") } + } + } + + @Test + fun inOrderOnObjectSuspendingCallsFailure() { + /* Given */ + val mock: SuspendFunctions = mock() + + /* When */ + runBlocking { + mock.stringResult("Value") + mock.stringResult("Other Value") + } + + /* Then */ + mock.inOrder { + verifyBlocking { stringResult("Other Value") } + assertThrows(AssertionError::class.java) { + verifyBlocking { stringResult("Value") } + } + } + } +} + +open class SomeClass { + suspend fun result(r: String) = withContext(Dispatchers.Default) { r } + open suspend fun delaying() = withContext(Dispatchers.Default) { + delay(100) + "Value" + } +} diff --git a/tests/src/test/kotlin/test/JUnit4Utils.kt b/tests/src/test/kotlin/test/JUnit4Utils.kt new file mode 100644 index 00000000..4e6b053f --- /dev/null +++ b/tests/src/test/kotlin/test/JUnit4Utils.kt @@ -0,0 +1,13 @@ +package test + +import junit.framework.AssertionFailedError + +/** Inspired by JUnit5, asserts an exception being thrown */ +inline fun assertThrows(block: () -> Unit): E { + return try { + block.invoke() + throw AssertionFailedError("No exception of type ${(E::class).simpleName} was thrown") + } catch (e: Throwable) { + e as? E ?: throw e + } +} diff --git a/tests/src/test/kotlin/test/MatchersTest.kt b/tests/src/test/kotlin/test/MatchersTest.kt index 9fe1eeea..eb87562d 100644 --- a/tests/src/test/kotlin/test/MatchersTest.kt +++ b/tests/src/test/kotlin/test/MatchersTest.kt @@ -17,7 +17,7 @@ class MatchersTest : TestBase() { class AnyMatchersTest { @Test fun anyString() { - mock().apply { + mock().apply { string("") verify(this).string(any()) } @@ -25,7 +25,7 @@ class MatchersTest : TestBase() { @Test fun anyNullableString() { - mock().apply { + mock().apply { nullableString("") verify(this).nullableString(any()) } @@ -33,7 +33,7 @@ class MatchersTest : TestBase() { @Test fun anyBoolean() { - mock().apply { + mock().apply { boolean(true) verify(this).boolean(any()) } @@ -41,7 +41,7 @@ class MatchersTest : TestBase() { @Test fun anyBooleanArray() { - mock().apply { + mock().apply { booleanArray(booleanArrayOf(true, false, false)) verify(this).booleanArray(any()) } @@ -49,7 +49,7 @@ class MatchersTest : TestBase() { @Test fun anyChar() { - mock().apply { + mock().apply { char('3') verify(this).char(any()) } @@ -57,7 +57,7 @@ class MatchersTest : TestBase() { @Test fun anyCharArray() { - mock().apply { + mock().apply { charArray(charArrayOf('3', '4', '5')) verify(this).charArray(any()) } @@ -65,7 +65,7 @@ class MatchersTest : TestBase() { @Test fun anyByte() { - mock().apply { + mock().apply { byte(3) verify(this).byte(any()) } @@ -73,7 +73,7 @@ class MatchersTest : TestBase() { @Test fun anyByteArray() { - mock().apply { + mock().apply { byteArray(byteArrayOf(3, 4, 5)) verify(this).byteArray(any()) } @@ -81,7 +81,7 @@ class MatchersTest : TestBase() { @Test fun anyShort() { - mock().apply { + mock().apply { short(3) verify(this).short(any()) } @@ -89,7 +89,7 @@ class MatchersTest : TestBase() { @Test fun anyShortArray() { - mock().apply { + mock().apply { shortArray(shortArrayOf(3, 4, 5)) verify(this).shortArray(any()) } @@ -97,7 +97,7 @@ class MatchersTest : TestBase() { @Test fun anyInt() { - mock().apply { + mock().apply { int(3) verify(this).int(any()) } @@ -105,7 +105,7 @@ class MatchersTest : TestBase() { @Test fun anyIntArray() { - mock().apply { + mock().apply { intArray(intArrayOf(3, 4, 5)) verify(this).intArray(any()) } @@ -113,7 +113,7 @@ class MatchersTest : TestBase() { @Test fun anyLong() { - mock().apply { + mock().apply { long(3) verify(this).long(any()) } @@ -121,7 +121,7 @@ class MatchersTest : TestBase() { @Test fun anyLongArray() { - mock().apply { + mock().apply { longArray(longArrayOf(3L, 4L, 5L)) verify(this).longArray(any()) } @@ -129,7 +129,7 @@ class MatchersTest : TestBase() { @Test fun anyFloat() { - mock().apply { + mock().apply { float(3f) verify(this).float(any()) } @@ -137,7 +137,7 @@ class MatchersTest : TestBase() { @Test fun anyFloatArray() { - mock().apply { + mock().apply { floatArray(floatArrayOf(3f, 4f, 5f)) verify(this).floatArray(any()) } @@ -145,7 +145,7 @@ class MatchersTest : TestBase() { @Test fun anyDouble() { - mock().apply { + mock().apply { double(3.0) verify(this).double(any()) } @@ -153,7 +153,7 @@ class MatchersTest : TestBase() { @Test fun anyDoubleArray() { - mock().apply { + mock().apply { doubleArray(doubleArrayOf(3.0, 4.0, 5.0)) verify(this).doubleArray(any()) } @@ -161,7 +161,7 @@ class MatchersTest : TestBase() { @Test fun anyClosedClass() { - mock().apply { + mock().apply { closed(Closed()) verify(this).closed(any()) } @@ -169,26 +169,25 @@ class MatchersTest : TestBase() { @Test fun anyClassClosedClass() { - mock().apply { + mock().apply { classClosed(Closed::class.java) verify(this).classClosed(any()) } } @Test - fun anyCoroutinesClosedClass() { - mock().apply { - runTest { - coroutinesClosed(Closed()) - verify(this@apply).coroutinesClosed(any()) - } - } + fun anySuspendMethodClosedClass() = runTest { + val mock = mock() + + mock.closed(Closed()) + + verify(mock).closed(any()) } /** https://github.com/nhaarman/mockito-kotlin/issues/27 */ @Test fun anyThrowableWithSingleThrowableConstructor() { - mock().apply { + mock().apply { throwableClass(ThrowableClass(IOException())) verify(this).throwableClass(any()) } @@ -196,7 +195,7 @@ class MatchersTest : TestBase() { @Test fun anyValueClass() { - mock().apply { + mock().apply { valueClass(ValueClass("Content")) verify(this).valueClass(any()) } @@ -204,7 +203,7 @@ class MatchersTest : TestBase() { @Test fun anyNeverVerifiesForNullValue() { - mock().apply { + mock().apply { nullableString(null) verify(this, never()).nullableString(any()) } @@ -214,7 +213,7 @@ class MatchersTest : TestBase() { class SpecialAnyMatchersTest { @Test fun anyClassArray() { - mock().apply { + mock().apply { closedArray(arrayOf(Closed())) verify(this).closedArray(anyArray()) } @@ -222,7 +221,7 @@ class MatchersTest : TestBase() { @Test fun anyNullableClassArray() { - mock().apply { + mock().apply { closedNullableArray(arrayOf(Closed(), null)) verify(this).closedNullableArray(anyArray()) } @@ -230,7 +229,7 @@ class MatchersTest : TestBase() { @Test fun anyStringVararg() { - mock().apply { + mock().apply { closedVararg(Closed(), Closed()) verify(this).closedVararg(anyVararg()) } @@ -238,7 +237,7 @@ class MatchersTest : TestBase() { @Test fun anyVarargMatching() { - mock().apply { + mock().apply { whenever(varargBooleanResult(anyVararg())).thenReturn(true) expect(varargBooleanResult()).toBe(true) } @@ -246,7 +245,7 @@ class MatchersTest : TestBase() { @Test fun anyValueClass_withValueClass() { - mock().apply { + mock().apply { valueClass(ValueClass("Content")) verify(this).valueClass(anyValueClass()) } @@ -255,7 +254,7 @@ class MatchersTest : TestBase() { @Test fun anyValueClass_withNonValueClass() { expectErrorWithMessage("kotlin.Float is not a value class.") on { - mock().apply { + mock().apply { float(10f) // Should throw an error because Float is not a value class float(anyValueClass()) @@ -265,7 +264,7 @@ class MatchersTest : TestBase() { @Test fun anyValueClass_withNestedValueClass() { - mock().apply { + mock().apply { nestedValueClass(NestedValueClass(ValueClass("Content"))) verify(this).nestedValueClass(anyValueClass()) } @@ -275,7 +274,7 @@ class MatchersTest : TestBase() { class AnyOrNullMatchersTest { @Test fun anyOrNullString() { - mock().apply { + mock().apply { string("") verify(this).string(anyOrNull()) } @@ -283,7 +282,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullNullableString() { - mock().apply { + mock().apply { nullableString("") verify(this).nullableString(anyOrNull()) } @@ -291,7 +290,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullNullableStringNullValue() { - mock().apply { + mock().apply { nullableString(null) verify(this).nullableString(anyOrNull()) } @@ -299,7 +298,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullBoolean() { - mock().apply { + mock().apply { boolean(false) verify(this).boolean(anyOrNull()) } @@ -307,7 +306,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullByte() { - mock().apply { + mock().apply { byte(3) verify(this).byte(anyOrNull()) } @@ -315,7 +314,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullChar() { - mock().apply { + mock().apply { char('a') verify(this).char(anyOrNull()) } @@ -323,7 +322,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullShort() { - mock().apply { + mock().apply { short(3) verify(this).short(anyOrNull()) } @@ -331,7 +330,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullInt() { - mock().apply { + mock().apply { int(3) verify(this).int(anyOrNull()) } @@ -339,7 +338,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullLong() { - mock().apply { + mock().apply { long(3) verify(this).long(anyOrNull()) } @@ -347,7 +346,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullFloat() { - mock().apply { + mock().apply { float(3f) verify(this).float(anyOrNull()) } @@ -355,7 +354,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullDouble() { - mock().apply { + mock().apply { double(3.0) verify(this).double(anyOrNull()) } @@ -363,7 +362,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullValueClass() { - mock().apply { + mock().apply { valueClass(ValueClass("Content")) verify(this).valueClass(anyOrNull()) } @@ -371,7 +370,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullNullableValueClass() { - mock().apply { + mock().apply { nullableValueClass(ValueClass("Content")) verify(this).nullableValueClass(anyOrNull()) } @@ -379,7 +378,7 @@ class MatchersTest : TestBase() { @Test fun anyOrNullNullableValueClassNullValue() { - mock().apply { + mock().apply { nullableValueClass(null) verify(this).nullableValueClass(anyOrNull()) } @@ -390,7 +389,7 @@ class MatchersTest : TestBase() { @Test fun eqString() { val value = "Value" - mock().apply { + mock().apply { string(value) verify(this).string(eq(value)) } @@ -399,7 +398,7 @@ class MatchersTest : TestBase() { @Test fun eqBoolean() { val value = true - mock().apply { + mock().apply { boolean(value) verify(this).boolean(eq(value)) } @@ -408,7 +407,7 @@ class MatchersTest : TestBase() { @Test fun eqBooleanArray() { val value = booleanArrayOf(true, false, false) - mock().apply { + mock().apply { booleanArray(value) verify(this).booleanArray(eq(value)) } @@ -417,7 +416,7 @@ class MatchersTest : TestBase() { @Test fun eqChar() { val value = '3' - mock().apply { + mock().apply { char(value) verify(this).char(eq(value)) } @@ -426,7 +425,7 @@ class MatchersTest : TestBase() { @Test fun eqCharArray() { val value = charArrayOf('3', '4', '5') - mock().apply { + mock().apply { charArray(value) verify(this).charArray(eq(value)) } @@ -435,7 +434,7 @@ class MatchersTest : TestBase() { @Test fun eqByte() { val value: Byte = 3 - mock().apply { + mock().apply { byte(value) verify(this).byte(eq(value)) } @@ -444,7 +443,7 @@ class MatchersTest : TestBase() { @Test fun eqByteArray() { val value = byteArrayOf(3, 4, 5) - mock().apply { + mock().apply { byteArray(value) verify(this).byteArray(eq(value)) } @@ -453,7 +452,7 @@ class MatchersTest : TestBase() { @Test fun eqShort() { val value: Short = 3 - mock().apply { + mock().apply { short(value) verify(this).short(eq(value)) } @@ -462,7 +461,7 @@ class MatchersTest : TestBase() { @Test fun eqShortArray() { val value = shortArrayOf(3, 4, 5) - mock().apply { + mock().apply { shortArray(value) verify(this).shortArray(eq(value)) } @@ -471,7 +470,7 @@ class MatchersTest : TestBase() { @Test fun eqInt() { val value = 3 - mock().apply { + mock().apply { int(value) verify(this).int(eq(value)) } @@ -480,7 +479,7 @@ class MatchersTest : TestBase() { @Test fun eqIntArray() { val value = intArrayOf(3, 4, 5) - mock().apply { + mock().apply { intArray(value) verify(this).intArray(eq(value)) } @@ -489,7 +488,7 @@ class MatchersTest : TestBase() { @Test fun eqLong() { val value = 3L - mock().apply { + mock().apply { long(value) verify(this).long(eq(value)) } @@ -498,7 +497,7 @@ class MatchersTest : TestBase() { @Test fun eqLongArray() { val value = longArrayOf(3L, 4L, 5L) - mock().apply { + mock().apply { longArray(value) verify(this).longArray(eq(value)) } @@ -507,7 +506,7 @@ class MatchersTest : TestBase() { @Test fun eqFloat() { val value = 3f - mock().apply { + mock().apply { float(value) verify(this).float(eq(value)) } @@ -516,7 +515,7 @@ class MatchersTest : TestBase() { @Test fun eqFloatArray() { val value = floatArrayOf(3f, 4f, 5f) - mock().apply { + mock().apply { floatArray(value) verify(this).floatArray(eq(value)) } @@ -525,7 +524,7 @@ class MatchersTest : TestBase() { @Test fun eqDouble() { val value = 3.0 - mock().apply { + mock().apply { double(value) verify(this).double(eq(value)) } @@ -534,7 +533,7 @@ class MatchersTest : TestBase() { @Test fun eqDoubleArray() { val value = doubleArrayOf(3.0, 4.0, 5.0) - mock().apply { + mock().apply { doubleArray(value) verify(this).doubleArray(eq(value)) } @@ -543,7 +542,7 @@ class MatchersTest : TestBase() { @Test fun eqClosedClass() { val value = Closed() - mock().apply { + mock().apply { closed(value) verify(this).closed(eq(value)) } @@ -552,27 +551,26 @@ class MatchersTest : TestBase() { @Test fun eqClassClosedClass() { val clazz = Closed::class.java - mock().apply { + mock().apply { classClosed(clazz) verify(this).classClosed(eq(clazz)) } } @Test - fun eqCoroutinesClosedClass() { + fun eqSuspendMethodClosedClass() = runTest { + val mock = mock() val value = Closed() - mock().apply { - runTest { - coroutinesClosed(value) - verify(this@apply).coroutinesClosed(eq(value)) - } - } + + mock.closed(value) + + verify(mock).closed(eq(value)) } @Test fun eqClassArray() { val value = arrayOf(Closed()) - mock().apply { + mock().apply { closedArray(value) verify(this).closedArray(eq(value)) } @@ -581,7 +579,7 @@ class MatchersTest : TestBase() { @Test fun eqNullableClassArray() { val value = arrayOf(Closed(), null) - mock().apply { + mock().apply { closedNullableArray(value) verify(this).closedNullableArray(eq(value)) } @@ -590,7 +588,7 @@ class MatchersTest : TestBase() { @Test fun eqValueClass() { val valueClass = ValueClass("Content") - mock().apply { + mock().apply { valueClass(valueClass) verify(this).valueClass(eq(valueClass)) } @@ -599,7 +597,7 @@ class MatchersTest : TestBase() { @Test fun eqNestedValueClass() { val nestedValueClass = NestedValueClass(ValueClass("Content")) - mock().apply { + mock().apply { nestedValueClass(nestedValueClass) verify(this).nestedValueClass(eq(nestedValueClass)) } @@ -609,7 +607,7 @@ class MatchersTest : TestBase() { class OtherMatchersTest { @Test fun listArgThat() { - mock().apply { + mock().apply { closedList(listOf(Closed(), Closed())) verify(this).closedList( argThat { @@ -621,7 +619,7 @@ class MatchersTest : TestBase() { @Test fun listArgForWhich() { - mock().apply { + mock().apply { closedList(listOf(Closed(), Closed())) verify(this).closedList( argForWhich { @@ -633,7 +631,7 @@ class MatchersTest : TestBase() { @Test fun listArgWhere() { - mock().apply { + mock().apply { closedList(listOf(Closed(), Closed())) verify(this).closedList( argWhere { @@ -645,7 +643,7 @@ class MatchersTest : TestBase() { @Test fun listArgCheck() { - mock().apply { + mock().apply { closedList(listOf(Closed(), Closed())) verify(this).closedList( check { @@ -657,7 +655,7 @@ class MatchersTest : TestBase() { @Test fun checkProperlyFails() { - mock().apply { + mock().apply { closedList(listOf(Closed(), Closed())) expectErrorWithMessage("Argument(s) are different!") on { @@ -672,7 +670,7 @@ class MatchersTest : TestBase() { @Test fun checkWithNullArgument_throwsError() { - mock().apply { + mock().apply { nullableString(null) expectErrorWithMessage("null").on { @@ -683,7 +681,7 @@ class MatchersTest : TestBase() { @Test fun isA_withNonNullableString() { - mock().apply { + mock().apply { string("") verify(this).string(isA()) } @@ -691,7 +689,7 @@ class MatchersTest : TestBase() { @Test fun isA_withNullableString() { - mock().apply { + mock().apply { nullableString("") verify(this).nullableString(isA()) } @@ -699,7 +697,7 @@ class MatchersTest : TestBase() { @Test fun same_withNonNullArgument() { - mock().apply { + mock().apply { string("") verify(this).string(same("")) } @@ -707,7 +705,7 @@ class MatchersTest : TestBase() { @Test fun same_withNullableNonNullArgument() { - mock().apply { + mock().apply { nullableString("") verify(this).nullableString(same("")) } @@ -715,7 +713,7 @@ class MatchersTest : TestBase() { @Test fun same_withNullArgument() { - mock().apply { + mock().apply { nullableString(null) verify(this).nullableString(same(null)) } @@ -724,7 +722,7 @@ class MatchersTest : TestBase() { @Test fun testVarargAnySuccess() { /* Given */ - val t = mock() + val t = mock() // a matcher to check if any of the varargs was equals to "b" val matcher = VarargAnyMatcher({ "b" == it }, String::class.java, true, false) @@ -738,7 +736,7 @@ class MatchersTest : TestBase() { @Test fun testVarargAnyFail() { /* Given */ - val t = mock() + val t = mock() // a matcher to check if any of the varargs was equals to "d" val matcher = VarargAnyMatcher({ "d" == it }, String::class.java, true, false) @@ -752,7 +750,7 @@ class MatchersTest : TestBase() { /** https://github.com/nhaarman/mockito-kotlin/issues/328 */ @Test fun testRefEqForNonNullableParameter() { - mock().apply { + mock().apply { /* When */ val array = intArrayOf(2, 3) intArray(array) diff --git a/tests/src/test/kotlin/test/MockingTest.kt b/tests/src/test/kotlin/test/MockingTest.kt index bfcf780f..699e58d1 100644 --- a/tests/src/test/kotlin/test/MockingTest.kt +++ b/tests/src/test/kotlin/test/MockingTest.kt @@ -112,7 +112,7 @@ class MockingTest : TestBase() { @Test fun mock_withCustomDefaultAnswer_parameterName() { /* Given */ - val mock = mock(defaultAnswer = Mockito.RETURNS_SELF) + val mock = mock(defaultAnswer = Mockito.RETURNS_SELF) /* When */ val result = mock.builderMethod() @@ -124,7 +124,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_extraInterfaces() { /* Given */ - val mock = mock( + val mock = mock( extraInterfaces = arrayOf(ExtraInterface::class) ) @@ -135,7 +135,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_name() { /* Given */ - val mock = mock(name = "myName") + val mock = mock(name = "myName") /* When */ expectErrorWithMessage("myName.stringResult()") on { @@ -146,7 +146,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_defaultAnswer() { /* Given */ - val mock = mock(defaultAnswer = Mockito.RETURNS_MOCKS) + val mock = mock(defaultAnswer = Mockito.RETURNS_MOCKS) /* When */ val result = mock.nonDefaultReturnType() @@ -158,7 +158,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_serializable() { /* Given */ - val mock = mock(serializable = true) + val mock = mock(serializable = true) /* Then */ expect(mock).toBeInstanceOf() @@ -167,7 +167,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_serializableMode() { /* Given */ - val mock = mock(serializableMode = BASIC) + val mock = mock(serializableMode = BASIC) /* Then */ expect(mock).toBeInstanceOf() @@ -178,18 +178,16 @@ class MockingTest : TestBase() { /* Given */ val out = mock() System.setOut(out) - val mock = mock(verboseLogging = true) + val mock = mock(verboseLogging = true) - try { - /* When */ + + /* When, Then */ + assertThrows { verify(mock).stringResult() - fail("Expected an exception") - } catch (e: WantedButNotInvoked) { - /* Then */ - argumentCaptor().apply { - verify(out).println(capture()) - expect(lastValue.toString()).toBe("methods.stringResult();") - } + } + argumentCaptor().apply { + verify(out).println(capture()) + expect(lastValue.toString()).toBe("synchronousFunctions.stringResult();") } } @@ -197,7 +195,7 @@ class MockingTest : TestBase() { fun mock_withSettingsAPI_invocationListeners() { /* Given */ var bool = false - val mock = mock(invocationListeners = arrayOf(InvocationListener { bool = true })) + val mock = mock(invocationListeners = arrayOf(InvocationListener { bool = true })) /* When */ mock.stringResult() @@ -209,7 +207,7 @@ class MockingTest : TestBase() { @Test fun mock_withSettingsAPI_stubOnly() { /* Given */ - val mock = mock(stubOnly = true) + val mock = mock(stubOnly = true) /* Expect */ expectErrorWithMessage("is a stubOnly() mock") on { @@ -247,7 +245,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_extraInterfaces() { /* Given */ - val mock = mock(extraInterfaces = arrayOf(ExtraInterface::class)) {} + val mock = mock(extraInterfaces = arrayOf(ExtraInterface::class)) {} /* Then */ expect(mock).toBeInstanceOf() @@ -256,7 +254,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_name() { /* Given */ - val mock = mock(name = "myName") {} + val mock = mock(name = "myName") {} /* When */ expectErrorWithMessage("myName.stringResult()") on { @@ -267,7 +265,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPIAndStubbing_name() { /* Given */ - val mock = mock(name = "myName") { + val mock = mock(name = "myName") { on { nullableStringResult() } doReturn "foo" } @@ -279,28 +277,28 @@ class MockingTest : TestBase() { } @Test - fun mockCoroutines_withClosedBooleanReturn_name() = runTest { + fun mockSuspendMethod_withClosedBooleanReturn_name() = runTest { /* Given */ - val mock = mock(name = "myName") { - onBlocking { coroutinesClosedBooleanResult(any()) } doReturn true + val mock = mock(name = "myName") { + onBlocking { closedBooleanResult(any()) } doReturn true } /* When */ - val result = mock.coroutinesClosedBooleanResult(Closed()) + val result = mock.closedBooleanResult(Closed()) /* Then */ expect(result).toBe(true) } @Test - fun mockCoroutines_withClassClosedBooleanReturn_name() = runTest { + fun mockSuspendMethod_withClassClosedBooleanReturn_name() = runTest { /* Given */ - val mock = mock(name = "myName") { - onBlocking { coroutinesClassClosedBooleanResult(any()) } doReturn true + val mock = mock(name = "myName") { + onBlocking { classClosedBooleanResult(any()) } doReturn true } /* When */ - val result = mock.coroutinesClassClosedBooleanResult(Closed::class.java) + val result = mock.classClosedBooleanResult(Closed::class.java) /* Then */ expect(result).toBe(true) @@ -309,7 +307,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_defaultAnswer() { /* Given */ - val mock = mock(defaultAnswer = Mockito.RETURNS_MOCKS) {} + val mock = mock(defaultAnswer = Mockito.RETURNS_MOCKS) {} /* When */ val result = mock.nonDefaultReturnType() @@ -321,7 +319,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_serializable() { /* Given */ - val mock = mock(serializable = true) {} + val mock = mock(serializable = true) {} /* Then */ expect(mock).toBeInstanceOf() @@ -330,7 +328,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_serializableMode() { /* Given */ - val mock = mock(serializableMode = BASIC) {} + val mock = mock(serializableMode = BASIC) {} /* Then */ expect(mock).toBeInstanceOf() @@ -341,17 +339,17 @@ class MockingTest : TestBase() { /* Given */ val out = mock() System.setOut(out) - val mock = mock(verboseLogging = true) {} + val mock = mock(verboseLogging = true) {} try { /* When */ verify(mock).stringResult() fail("Expected an exception") - } catch (e: WantedButNotInvoked) { + } catch (_: WantedButNotInvoked) { /* Then */ argumentCaptor().apply { verify(out).println(capture()) - expect(lastValue.toString()).toBe("methods.stringResult();") + expect(lastValue.toString()).toBe("synchronousFunctions.stringResult();") } } } @@ -360,7 +358,7 @@ class MockingTest : TestBase() { fun mockStubbing_withSettingsAPI_invocationListeners() { /* Given */ var bool = false - val mock = mock(invocationListeners = arrayOf(InvocationListener { bool = true })) {} + val mock = mock(invocationListeners = arrayOf(InvocationListener { bool = true })) {} /* When */ mock.stringResult() @@ -372,7 +370,7 @@ class MockingTest : TestBase() { @Test fun mockStubbing_withSettingsAPI_stubOnly() { /* Given */ - val mock = mock(stubOnly = true) {} + val mock = mock(stubOnly = true) {} /* Expect */ expectErrorWithMessage("is a stubOnly() mock") on { diff --git a/tests/src/test/kotlin/test/OngoingStubbingTest.kt b/tests/src/test/kotlin/test/OngoingStubbingTest.kt index 28917202..b7147d39 100644 --- a/tests/src/test/kotlin/test/OngoingStubbingTest.kt +++ b/tests/src/test/kotlin/test/OngoingStubbingTest.kt @@ -2,32 +2,21 @@ package test import com.nhaarman.expect.expect import com.nhaarman.expect.expectErrorWithMessage -import com.nhaarman.expect.fail +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest import org.junit.Assume.assumeFalse import org.junit.Test import org.mockito.Mockito import org.mockito.exceptions.misusing.UnfinishedStubbingException -import org.mockito.kotlin.any -import org.mockito.kotlin.argThat -import org.mockito.kotlin.check -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doReturnConsecutively -import org.mockito.kotlin.doThrow -import org.mockito.kotlin.mock -import org.mockito.kotlin.stub -import org.mockito.kotlin.stubbing -import org.mockito.kotlin.whenever +import org.mockito.kotlin.* import org.mockito.stubbing.Answer class OngoingStubbingTest : TestBase() { - @Test - fun testOngoingStubbing_methodCall() { + fun `should stub function call`() { /* Given */ - val mock = mock() - mock { - on(mock.stringResult()).doReturn("A") + val mock = mock { + on { stringResult() } doReturn "A" } /* When */ @@ -38,9 +27,67 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_builder() { + fun `should stub consecutive function calls`() { + /* Given */ + val mock = mock { + on { stringResult() }.doReturn ("A", "B", "C") + } + + /* When */ + val result =(1..3).map { _ -> + mock.stringResult() + } + + /* Then */ + expect(result).toBe(listOf("A", "B", "C")) + } + + @Test + fun `should stub consecutive function calls by a list of answers`() { /* Given */ - val mock = mock { mock -> + val mock = mock { + on { stringResult() } doReturnConsecutively listOf("A", "B", "C") + } + + /* When */ + val result =(1..3).map { _ -> + mock.stringResult() + } + + /* Then */ + expect(result).toBe(listOf("A", "B", "C")) + } + + @Test + fun `should stub builder method returning mock itself via defaultAnswer`() { + /* Given */ + val mock = mock(defaultAnswer = Mockito.RETURNS_SELF) + + /* When */ + val result = mock.builderMethod() + + /* Then */ + expect(result).toBeTheSameAs(mock) + } + + @Test + fun `should stub builder method returning mock itself via answer`() { + /* Given */ + val mock = mock { + on { builderMethod() } doAnswer Mockito.RETURNS_SELF + } + + /* When */ + val result = mock.builderMethod() + + /* Then */ + expect(result).toBeTheSameAs(mock) + } + + @Test + fun `should stub builder method returning mock itself`() { + /* Given */ + val mock = mock { mock -> on { builderMethod() } doReturn mock } @@ -52,9 +99,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_nullable() { + fun `should stub function call with nullable result`() { /* Given */ - val mock = mock { + val mock = mock { on { nullableStringResult() } doReturn "Test" } @@ -66,90 +113,76 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_doThrow() { + fun `should throw exception instance on function call`() { /* Given */ - val mock = mock { + val mock = mock { on { builderMethod() } doThrow IllegalArgumentException() } - try { - /* When */ + /* When, Then */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: IllegalArgumentException) { } } @Test - fun testOngoingStubbing_doThrowClass() { + fun `should throw exception class on function call`() { /* Given */ - val mock = mock { + val mock = mock { on { builderMethod() } doThrow IllegalArgumentException::class } - try { - /* When */ + /* When, Then */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: IllegalArgumentException) { } } @Test - fun testOngoingStubbing_doThrowVarargs() { + fun `should throw exception instances on consecutive function calls`() { /* Given */ - val mock = mock { + val mock = mock { on { builderMethod() }.doThrow( IllegalArgumentException(), UnsupportedOperationException() ) } - try { - /* When */ + /* When, Then */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: IllegalArgumentException) { } - - try { - /* When */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: UnsupportedOperationException) { } } @Test - fun testOngoingStubbing_doThrowClassVarargs() { + fun `should throw exception classes on consecutive function calls`() { /* Given */ - val mock = mock { + val mock = mock { on { builderMethod() }.doThrow( IllegalArgumentException::class, UnsupportedOperationException::class ) } - try { - /* When */ + /* When, Then */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: IllegalArgumentException) { } - - try { - /* When */ + assertThrows { mock.builderMethod() - fail("No exception thrown") - } catch (e: UnsupportedOperationException) { } } @Test - fun testOngoingStubbing_doAnswer_lambda() { + fun `should stub function call with result from answer instance`() { /* Given */ - val mock = mock { - on { stringResult() } doAnswer { "result" } + val answer: Answer = Answer { "result" } + + val mock = mock { + on { stringResult() } doAnswer answer } /* When */ @@ -160,10 +193,10 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_doAnswer_instance() { + fun `should stub function call with result from lambda`() { /* Given */ - val mock = mock { - on { stringResult() } doAnswer Answer { "result" } + val mock = mock { + on { stringResult() } doAnswer { "result" } } /* When */ @@ -174,23 +207,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_doAnswer_returnsSelf() { + fun `should stub function call with result from lambda with argument`() { /* Given */ - val mock = mock { - on { builderMethod() } doAnswer Mockito.RETURNS_SELF - } - - /* When */ - val result = mock.builderMethod() - - /* Then */ - expect(result).toBe(mock) - } - - @Test - fun testOngoingStubbing_doAnswer_withArgument() { - /* Given */ - val mock = mock { + val mock = mock { on { stringResult(any()) } doAnswer { "${it.arguments[0]}-result" } } @@ -202,9 +221,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_doAnswer_withDestructuredArgument() { + fun `should stub function call with result from lambda with deconstructed argument`() { /* Given */ - val mock = mock { + val mock = mock { on { stringResult(any()) } doAnswer { (s: String) -> "$s-result" } } @@ -216,9 +235,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOngoingStubbing_doAnswer_withDestructuredArguments() { + fun `should stub function call with result from lambda with deconstructed arguments`() { /* Given */ - val mock = mock { + val mock = mock { on { varargBooleanResult(any(), any()) } doAnswer { (a: String, b: String) -> a == b.trim() } @@ -232,8 +251,8 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testMockStubbingAfterCreatingMock() { - val mock = mock() + fun `should stub already created mock`() { + val mock = mock() //create stub after creation of mock mock.stub { @@ -248,9 +267,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun testOverrideDefaultStub() { + fun `should override stub of already created mock`() { /* Given mock with stub */ - val mock = mock { + val mock = mock { on { stringResult() } doReturn "result1" } @@ -267,9 +286,9 @@ class OngoingStubbingTest : TestBase() { } @Test - fun stubbingTwiceWithArgumentMatchers() { + fun `should stub 2 method calls determined by ArgumentMatchers`() { /* When */ - val mock = mock { + val mock = mock { on { stringResult(argThat { this == "A" }) } doReturn "A" on { stringResult(argThat { this == "B" }) } doReturn "B" } @@ -280,97 +299,156 @@ class OngoingStubbingTest : TestBase() { } @Test - fun stubbingRealObject() { - val notAMock = "" - - /* Expect */ - expectErrorWithMessage("is not a mock!").on { - notAMock.stub { } - } - } - - @Test - fun stubbingTwiceWithCheckArgumentMatchers_throwsException() { - /* Expect */ - expectErrorWithMessage("null").on { - mock { + // stubbingTwiceWithCheckArgumentMatchers_throwsException + fun `should throw when check ArgumentMatcher is applied twice`() { + val exception: IllegalStateException = assertThrows { + mock { on { stringResult(check { }) } doReturn "A" on { stringResult(check { }) } doReturn "B" } } + expect(exception.message).toContain("The argument passed to the predicate was null") } @Test - fun doReturn_withSingleItemList() { + fun `should stub function call with integer result`() { /* Given */ - val mock = mock { - on { stringResult() } doReturnConsecutively listOf("a", "b") + val mock = mock> { + onGeneric { genericMethod() } doReturn 2 } /* Then */ - expect(mock.stringResult()).toBe("a") - expect(mock.stringResult()).toBe("b") + expect(mock.genericMethod()).toBe(2) } @Test - fun doReturn_throwsNPE() { - assumeFalse(mockMakerInlineEnabled()) - expectErrorWithMessage("look at the stack trace below") on { + fun `should stub nullable function call with string result`() { + val mock = mock> { + onGeneric { nullableReturnType() } doReturn "Test" + } - /* When */ - mock { - on { throwsNPE() } doReturn "result" - } + expect(mock.nullableReturnType()).toBe("Test") + } + + @Test + fun `should stub function call with value class result`() { + /* Given */ + val valueClass = ValueClass("A") + val mock = mock { + on { valueClassResult() } doReturn valueClass } + + /* When */ + val result: ValueClass = mock.valueClassResult() + + /* Then */ + expect(result).toBe(valueClass) } @Test - fun doReturn_withGenericIntReturnType_onGeneric() { + fun `should stub function call with nullable value class result`() { /* Given */ - val mock = mock> { - onGeneric { genericMethod() } doReturn 2 + val valueClass = ValueClass("A") + val mock = mock { + on { nullableValueClassResult() } doReturn valueClass } + /* When */ + val result: ValueClass? = mock.nullableValueClassResult() + /* Then */ - expect(mock.genericMethod()).toBe(2) + expect(result).toBe(valueClass) } @Test - fun doReturn_withGenericNullableReturnType_onGeneric() { - val m = mock> { - onGeneric { nullableReturnType() } doReturn "Test" + fun `should stub function call with nested value class result`() { + /* Given */ + val nestedValueClass = NestedValueClass(ValueClass("A")) + val mock = mock { + on { nestedValueClassResult() } doReturn nestedValueClass } - expect(m.nullableReturnType()).toBe("Test") + /* When */ + val result: NestedValueClass = mock.nestedValueClassResult() + + /* Then */ + expect(result).toBe(nestedValueClass) + expect(result.value).toBe(nestedValueClass.value) } @Test - fun stubbingExistingMock() { + fun `should stub consecutive function call with value class results`() { /* Given */ - val mock = mock() + val valueClassA = ValueClass("A") + val valueClassB = ValueClass("B") + val mock = mock { + on { valueClassResult() }.doReturnConsecutively(valueClassA, valueClassB) + } /* When */ - stubbing(mock) { - on { stringResult() } doReturn "result" + val result1 = mock.valueClassResult() + val result2 = mock.valueClassResult() + + /* Then */ + expect(result1).toBe(valueClassA) + expect(result2).toBe(valueClassB) + } + + @Test + fun `should stub suspendable function call with value class result`() = + runTest { + /* Given */ + val valueClass = ValueClass("A") + val mock = mock { + on(mock.valueClassResult()) doSuspendableAnswer { + delay(1) + valueClass + } + } + + /* When */ + val result: ValueClass = mock.valueClassResult() + + /* Then */ + expect(result).toBe(valueClass) + } + + @Test + fun `should stub function call to make real function call into mock`() { + /* Given */ + val mock = mock { + on { valueClassResult(any()) }.doCallRealMethod() } + /* When */ + val result = mock.valueClassResult(ValueClass("Value")) + /* Then */ - expect(mock.stringResult()).toBe("result") + expect(result.content).toBe("Result: Value") } @Test - fun testMockitoStackOnUnfinishedStubbing() { + fun `should throw when stubbing is incomplete`() { /* Given */ val mock = mock() whenever(mock.stringResult()) /* When */ - try { + val exception = assertThrows { mock.stringResult() - } catch (e: UnfinishedStubbingException) { - /* Then */ - expect(e.message).toContain("Unfinished stubbing detected here:") - expect(e.message).toContain("-> at test.OngoingStubbingTest.testMockitoStackOnUnfinishedStubbing") + } + expect(exception.message).toContain("Unfinished stubbing detected here:") + } + + @Test + fun doReturn_throwsNPE() { + assumeFalse("mockMakerInline is not enabled", mockMakerInlineEnabled()) + expectErrorWithMessage("look at the stack trace below") on { + + /* When */ + mock { + on { throwsNPE() } doReturn "result" + } } } } diff --git a/tests/src/test/kotlin/test/StubberTest.kt b/tests/src/test/kotlin/test/StubberTest.kt index 7b8f6af9..5144cb7e 100644 --- a/tests/src/test/kotlin/test/StubberTest.kt +++ b/tests/src/test/kotlin/test/StubberTest.kt @@ -9,7 +9,7 @@ class StubberTest : TestBase() { @Test fun testDoAnswer() { - val mock = mock() + val mock = mock() doAnswer { "Test" } .whenever(mock) @@ -41,7 +41,7 @@ class StubberTest : TestBase() { @Test fun testDoReturnValue() { - val mock = mock() + val mock = mock() doReturn("test").whenever(mock).stringResult() @@ -50,7 +50,7 @@ class StubberTest : TestBase() { @Test fun testDoReturnNullValue() { - val mock = mock() + val mock = mock() doReturn(null).whenever(mock).stringResult() @@ -59,7 +59,7 @@ class StubberTest : TestBase() { @Test fun testDoReturnNullValues() { - val mock = mock() + val mock = mock() doReturn(null, null).whenever(mock).stringResult() @@ -69,7 +69,7 @@ class StubberTest : TestBase() { @Test fun testDoReturnValues() { - val mock = mock() + val mock = mock() doReturn("test", "test2").whenever(mock).stringResult() @@ -86,7 +86,7 @@ class StubberTest : TestBase() { try { mock.go() throw AssertionError("Call should have thrown.") - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { } } @@ -103,7 +103,7 @@ class StubberTest : TestBase() { @Test fun testStubberOnBlockExtension() { - val mock = mock { + val mock = mock { doReturn("Test").on { stringResult() } } diff --git a/tests/src/test/kotlin/test/StubbingTest.kt b/tests/src/test/kotlin/test/StubbingTest.kt new file mode 100644 index 00000000..1b22fb6e --- /dev/null +++ b/tests/src/test/kotlin/test/StubbingTest.kt @@ -0,0 +1,261 @@ +package test + +import com.nhaarman.expect.expect +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.exceptions.misusing.NotAMockException +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doSuspendableAnswer +import org.mockito.kotlin.mock +import org.mockito.kotlin.onBlocking +import org.mockito.kotlin.stub +import org.mockito.kotlin.stubbing +import org.mockito.kotlin.whenever +import org.mockito.kotlin.wheneverBlocking + + +class StubbingTest { + @Test + fun `should stub sync method call as part of mock creation`() { + /* Given */ + val mock = mock { + on { stringResult() } doReturn "A" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync method call in reverse manner as part of mock creation`() { + val mock = mock { + doReturn("A").on { stringResult() } + } + + expect(mock.stringResult()).toBe("A") + } + + @Test + fun `should stub sync method call with whenever`() { + /* Given */ + val mock = mock() + whenever(mock.stringResult()).doReturn("A") + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync method call with whenever and lambda`() { + /* Given */ + val mock = mock() + whenever { mock.stringResult() } doReturn "A" + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync method call with reverse whenever`() { + /* Given */ + val mock = mock() + doReturn("A").whenever(mock).stringResult() + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync method call using stub extension method`() { + /* Given */ + val mock = mock() + mock.stub{ + on { stringResult() } doReturn "A" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync method call using stubbing method`() { + /* Given */ + val mock = mock() + stubbing(mock) { + on { stringResult() } doReturn "A" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call with wheneverBlocking as part of mock creation`() { + /* Given */ + val mock = mock { mock -> + wheneverBlocking { mock.stringResult() } doReturn "A" + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call in reverse manner with wheneverBlocking as part of mock creation`() { + /* Given */ + val mock = mock { mock -> + doReturn( "A").wheneverBlocking(mock) { mock.stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call in reverse manner with onBlocking as part of mock creation`() { + /* Given */ + val mock = mock { mock -> + doReturn( "A").onBlocking(mock) { mock.stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call in reverse manner with whenever as part of mock creation`() = runTest{ + /* Given */ + val mock = mock { mock -> + doReturn( "A").whenever(mock).stringResult() + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync function call within a suspendable lambda`() { + /* Given */ + val mock = mock { + onBlocking { stringResult() } doReturn "A" + } + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub sync function call within a suspendable lambda of wheneverBlocking`() { + /* Given */ + val mock = mock() + wheneverBlocking { mock.stringResult() } doReturn "A" + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspend function call with a call to the sync version of whenever`() = runTest { + /* Given */ + val mock = mock() + whenever (mock.stringResult()) doReturn "A" + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should throw when stubbing other mock then being created`() { + /* Given */ + val mock = mock() + + /* When, Then */ + val exception: IllegalArgumentException = assertThrows { + mock { + on(mock.stringResult()).doReturn("A") + } + } + expect(exception.message).toContain("Stubbing of another mock is not allowed") + } + + @Test + fun `should throw when trying to stub a real object with stub extension method`() { + /* Given */ + val notAMock = Open() + + /* When, Then */ + val exception: NotAMockException = assertThrows { + notAMock.stub { } + } + expect(exception.message).toContain("Stubbing target is not a mock!") + } + + @Test + fun `should throw when trying to stub a real object with stubbing method`() { + /* Given */ + val notAMock = Open() + + /* When, Then */ + val exception: NotAMockException = assertThrows { + stubbing(notAMock) { } + } + expect(exception.message).toContain("Stubbing target is not a mock!") + } + + @Test + fun `should provide backwards support to stub suspendable function call with 'whenever' method`() = runTest { + /* Given */ + val mock = mock() + whenever(mock.stringResult()).doSuspendableAnswer { + delay(0) + "A" + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } +} diff --git a/tests/src/test/kotlin/test/VerificationTest.kt b/tests/src/test/kotlin/test/VerificationTest.kt index db5620fc..62acb0c8 100644 --- a/tests/src/test/kotlin/test/VerificationTest.kt +++ b/tests/src/test/kotlin/test/VerificationTest.kt @@ -10,7 +10,7 @@ class VerificationTest : TestBase() { @Test fun atLeastXInvocations() { - mock().apply { + mock().apply { string("") string("") @@ -20,7 +20,7 @@ class VerificationTest : TestBase() { @Test fun testAtLeastOnce() { - mock().apply { + mock().apply { string("") string("") @@ -30,7 +30,7 @@ class VerificationTest : TestBase() { @Test fun atMostXInvocations() { - mock().apply { + mock().apply { string("") string("") @@ -40,7 +40,7 @@ class VerificationTest : TestBase() { @Test fun testCalls() { - mock().apply { + mock().apply { string("") string("") @@ -68,7 +68,7 @@ class VerificationTest : TestBase() { @Test fun testInOrderWithReceiver() { /* Given */ - val mock = mock() + val mock = mock() /* When */ mock.string("") @@ -84,7 +84,7 @@ class VerificationTest : TestBase() { @Test fun testClearInvocations() { - val mock = mock().apply { + val mock = mock().apply { string("") } @@ -96,7 +96,7 @@ class VerificationTest : TestBase() { @Test fun testDescription() { try { - mock().apply { + mock().apply { verify(this, description("Test")).string(any()) } throw AssertionError("Verify should throw Exception.") @@ -107,7 +107,7 @@ class VerificationTest : TestBase() { @Test fun testAfter() { - mock().apply { + mock().apply { int(3) verify(this, after(10)).int(3) }