I'm attempting to add coroutines to our Android application but I'm hitting a snag with our mocking framework. My interface has a suspend function like so:
interface MyInterface {
suspend fun makeNetworkCall(id: String?) : Response?
}
Here is how I'm attempting to verify the code was executed in my unit test
runBlocking {
verify(myInterface).makeNetworkCall(Matchers.anyObject())
}
When I do this I'm getting the following error
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.myproject.MyTest$testFunction$1.invokeSuspend(MyTest.kt:66)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
Is there another way we should verify that the appropriate method is being called when using coroutines? Any help would be appreciated.
I tried to write similar test using the code you have provided. Initially, I got same error as yours. However, when I used mockito-core v2.23.4, the tests were passed.
Here are quick steps which you can try:
add testCompile "org.mockito:mockito-core:2.23.4" to the dependencies list in your build.gradle file.
Run the tests again, and you should not get similar error.
As Matchers.anyObject() is deprecated, I used ArgumentMatchers.any().
Below you can see the client code:
data class Response(val message: String)
interface MyInterface {
suspend fun makeNetworkCall(id: String?) : Response?
}
class Client(val myInterface: MyInterface) {
suspend fun doSomething(id: String?) {
myInterface.makeNetworkCall(id)
}
}
Here is the test code:
class ClientTest {
var myInterface: MyInterface = mock(MyInterface::class.java)
lateinit var SUT: Client
#Before
fun setUp() {
SUT = Client(myInterface)
}
#Test
fun doSomething() = runBlocking<Unit> {
// Act
SUT.doSomething("123")
// Verify
Mockito.verify(myInterface).makeNetworkCall(ArgumentMatchers.any())
}
}
Related
I have a custom Logger used thorough the application. It is doing more, but it can be simplified as:
class MyLogger {
companion object {
fun d(tag: String, msg: String): Int {
return Log.d(tag, msg)
}
}
}
Again, for simplicity, asume a function like the following:
fun addWithLogger(val1: Int, val2: Int): Int {
MyLogger.d("TEST", "adding $val2 to $val1")
return (val1 + val2).also {
MyLogger.d("TEST", "result $it")
}
}
I am trying to test the function using JUnit, and mock the MyLogger.d function using mockk library:
#Test
fun additionWithLogger_isCorrect() {
val testClass = TestClass()
mockkStatic(MyLogger::class)
every { MyLogger.d(any(), any()) } returns 0
assertEquals(5, testClass.addWithLogger(2, 3))
}
I have tried with mockkStatic, mockkObject, with MyLogger.Companion etc, but all my attempts are resulting in something simillar:
Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
java.lang.RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.util.Log.d(Log.java)
at example.MyLogger$Companion.d(MyLogger.kt:8)
at example.ExampleUnitTest$additionWithLogger_isCorrect$1.invoke(ExampleUnitTest.kt:25)
at example.ExampleUnitTest$additionWithLogger_isCorrect$1.invoke(ExampleUnitTest.kt:25)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:25)
at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithRethrow$1.invoke(RecordedBlockEvaluator.kt:78)
at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:40)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalEvery(API.kt:94)
at io.mockk.MockKKt.every(MockK.kt:143)
at example.ExampleUnitTest.additionWithLogger_isCorrect(ExampleUnitTest.kt:25)
...
It seems, that the MyLogger.d function is still being invoked, despite I am trying to mock it? What am I missing?
I have tried mockito and mockk libraries, but with no luck. I would like to test the functionality using Unit tests on PC. Tests are running ok as instrumental on Android device.
Code to be tested is already written, so I do not have much possibilities to adjust the MyLogger inside...
I have the following code:
suspend fun initialize(sdk: Sdk) =
suspendCoroutine<Unit> { continuation ->
try {
sdk.initialize(
callback = { continuation.resume(Unit) },
onFailure = { error -> continuation.resumeWithException(SdkException(error.message)) })
} catch (exception: Exception) {
continuation.resumeWithException(
SdkException("Crash inside SDK", exception)
)
}
}
The Sdk is a third-party library. I'm using suspendCoroutine to suspend the coroutine and resume when the sdk finishes initializing.
Everything works fine but when I try to write a unit test like this I get the following IllegalStateException: This job has not completed yet. I'm using mockito-kotlin to write the following test:
#Test
fun `should initialize sdk correctly`() = runBlockingTest {
val sdk = mock<Sdk>()
initializeSdk(sdk)
verify(sdk).initialize(any(), any())
}
Basically what I want to do is to be able to test the resume and resumeWithException
Personally I'm not a fan of mocks. If Sdk is an interface, you could just provide your own test implementation to perform your tests (for success and error results). You can control exactly when/if the callback is called etc.
If Sdk is a class that you can't control, you could create an interface to abstract the Sdk class away, and make your initialize method use your own interface instead. However I have to admit this is not ideal.
If you stick with mocking, usually mocking libraries have a way for you to use invocation arguments to mock responses to method calls. With Mockito, it should be something like:
val sdk = mock<Sdk> {
on { initialize(any(), any()) } doAnswer { invocation ->
#Suppress("UNCHECKED_CAST")
val successCallback = invocation.arguments[0] as (() -> Unit) // use callback's function type here
successCallback.invoke()
}
}
(although I'm not an expert in Mockito, so there may be more concise or type-safe ways :D)
I'm working with Kotlin (for Android), along with jUnit and Koin.
I have an interface to consume that looks like this:
interface MyInterface {
fun vehicle(id: String, block: VehicleInterface.() -> Unit)
}
interface VehicleInterface {
fun requestVersion()
}
The code that calls this method looks like this:
import com.domain.protocol.thelibrary.sdk.MyInterface
// The below is injected via Koin
class SomeClass(private val myInterface: MyInterface) {
fun functionIWantToUnitTest(id: String) {
myInterface.vehicle(id) {
requestVersion()
}
}
}
I would like to unit test that when I call functionIWantToUnitTest and pass in the id "1" then myInterface.vehicle is called with id "1" and I'd also like to verify that requestVersion is then called.
I wrote a unit test as such:
#Test
fun `Ensure functionIWantToUnitTest calls requestVersion with the correct id`() {
every { myInterface.vehicle("1", any()) } returns Unit
someClass.functionIWantToUnitTest("1")
verifySequence {
myInterface.vehicle("1") {
requestVersion()
}
}
}
This fails as such:
java.lang.AssertionError: Verification failed: call 1 of 1:
MyInterface(myInterface#1).vehicle(eq(1), eq(lambda {}))). Only one matching call to
MyInterface(myInterface#1)/vehicle(String, Function1) happened, but arguments are not
matching:
[0]: argument: 1, matcher: eq(1), result: +
[1]: argument: lambda {}, matcher: eq(lambda {}), result: -
Can anyone please help me to understand what I'm doing wrong...
Any help gratefully accepted.
I am trying to unit test the following class:
class UserProfileDetailsAnalyticUseCaseImp #Inject constructor(private val analyticsProvider: AnalyticsProvider) : UserProfileDetailsAnalyticUseCase {
override fun execute(cdsCustomer: CDSCustomer) {
with(analyticsProvider) {
log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
log(AnalyticEvent.UserEmail(cdsCustomer.email))
}
}
}
And this is my unit test:
class UserProfileDetailsAnalyticUseCaseImpTest {
private lateinit var userProfileDetailsAnalyticUseCaseImp: UserProfileDetailsAnalyticUseCaseImp
private val analyticsProviders: AnalyticsProvider = mock()
#Before
fun setUp() {
userProfileDetailsAnalyticUseCaseImp = UserProfileDetailsAnalyticUseCaseImp(analyticsProviders)
}
#Test
fun `should send analytic event`() {
// Arrange
val cdsCustomer = CDSCustomer(
id = Random.nextInt(0, 100000),
email = UUID.randomUUID().toString())
val userIdCapture= argumentCaptor<AnalyticEvent.UserId>()
val userEmailCapture= argumentCaptor<AnalyticEvent.UserEmail>()
// Act
userProfileDetailsAnalyticUseCaseImp.execute(cdsCustomer)
// Assert
verify(analyticsProviders, atLeastOnce()).log(userIdCapture.capture())
verify(analyticsProviders, atLeastOnce()).log(userEmailCapture.capture())
assertThat(userIdCapture.firstValue.userId).isEqualTo(cdsCustomer.id.toString())
assertThat(userEmailCapture.firstValue.email).isEqualTo(cdsCustomer.email)
}
}
The error I get is the following:
AnalyticEvent$UserId cannot be cast to AnalyticEvent$UserEmail
I am suspecting that because class under test is creating a new object for each log method they will not be the same for the verified methods in the unit test
i.e log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
As a new AnaltyicEvent.UserId will be created and just for the same AnalyticProvider mock
Many thanks for any suggetions
In the documentation of ArgumentCaptor we can read that:
This utility class doesn't do any type checks. The generic
signatures are only there to avoid casting in your code.
Moreover CapturingMatcher which is used for collecting captured arguments has a method which matches all objects:
public boolean matches(Object argument) {
return true;
}
It means that it is normal behaviour and even when we specify concrete type of captor it will record all arguments passed.
Of course all these arguments have to inherit from the same base class because in other case capture method will cause compilation error.
So, both your captors record two arguments.
To fix class cast exception for your test you can assert secondValue for email.
assertThat(userEmailCapture.secondValue.email).isEqualTo(cdsCustomer.email)
You can also stop using argument captors and simply verify invocations of log method.
verify(analyticsProviders).log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
verify(analyticsProviders).log(AnalyticEvent.UserEmail(cdsCustomer.email))
I am trying to write a unit test for a implementation of an abstract class I wrote. The method I'm trying to mock takes a lambda as it's only parameter. I'm trying to capture this lambda, so I can invoke it and get the result.
This is the method I'm trying to mock:
protected fun update(block: suspend S.() -> S?): Unit
I am using an extension function in my tests like this:
suspend inline fun <reified T : Model<S>, S : State> T.blah(
state: S,
block: (T) -> Unit
): S? {
val spy = spyk(this, recordPrivateCalls = true)
val slot = slot<suspend S.() -> S?>()
every { spy["update"](capture(slot)) } answers { Unit }
block(spy)
return slot.captured.invoke(state)
}
So I am creating a spy, then a slot, then when the update function is called, capture it so that it blocks the actual class from performing the call. Then I invoke the lambda myself and return the value.
However I keep getting this error:
io.mockk.MockKException: can't find function update(kotlin.jvm.functions.Function2$Subclass1#6bfa228c) for dynamic call
at io.mockk.InternalPlatformDsl.dynamicCall(InternalPlatformDsl.kt:122)
at io.mockk.MockKMatcherScope$DynamicCall.invoke(API.kt:1969)
I followed the stacktrace and set a breakpoint in the InternalPlatformDsl.kt class, and traced it to this block of code:
for ((idx, param) in it.parameters.withIndex()) {
val classifier = param.type.classifier
val matches = when (classifier) {
is KClass<*> -> classifier.isInstance(params[idx])
is KTypeParameter -> classifier.upperBounds.anyIsInstance(params[idx])
else -> false
}
if (!matches) {
return#firstOrNull false
}
}
It successfully matches the first parameter which is the class under test Model in this case, but it fails matching the second parameter because it is wrapped in the capture function.
Any ideas on how I can intercept this update call?
I'm using the latest version of mockk, and JUnit 4