Let's say we have the following function to test
fun loadData(dataId: Long, completion: (JsonElement?, Exception?) -> Unit) {
underlayingApi.post(url = "some/rest/url",
completion = { rawResult, exception ->
val processedResult = processJson(rawResult)
completion(processedResult, exception)
})
}
It's clear to me how to mock, inject, stub and verify the calls to underlayingApi.
How to verify the result returned via completion(processedResult, exception)?
To test the lambdas behavior, the underlayingApi has to be mocked where the lambda is invoked via the InvoactionOnMock object like this.
`when`(underlayingApi.post(eq("some/rest/url"),
any())).thenAnswer {
val argument = it.arguments[1]
val completion = argument as ((rawResult: String?, exception: Exception?) -> Unit)
completion.invoke("result", null)
}
This leads to the invocation of the callback within the object under test. Now to check if the callback out of the object under test is working verify it like that.
objUnderTest.loadData(id,
{ json, exception ->
assert....
})
Building on Martin's answer, here is my approach without lint warnings:
import com.nhaarman.mockito_kotlin.*
#Test
fun loadData() {
val mockUnderlyingApi: UnderlayingApi = mock()
val underTest = ClassBeingTested()
underTest.underlayingApi = mockUnderlyingApi
whenever(mockUnderlyingApi.post(eq("some/rest/url"), any())).thenAnswer {
val completion = it.getArgument<((rawResult: String?, exception: Exception?) -> Unit)>(1)
completion.invoke("result", null)
}
underTest.loadData(0L,
{ jsonElement, exception ->
// Check whatever you need to check
// on jsonElement an/or exception
})
}
Related
How can I rewrite in Kotlin a (Throwable) -> Unit into a (Throwable) -> Void?
The thing I need to achieve is make the following code compile:
fun myKotlinMethod(onError: (Throwable) -> Unit) { // Can not change here
val onErrorJavaCompatible: (Throwable) -> Void = { t -> onError.invoke(t) } // Compilation error
MyJavaLibrary.aMethod(onErrorJavaCompatible) // Can not change here
}
If you want to use a Kotlin function as a Java functional interface parameter, you need to convert it to that interface using SAM conversion, or you need to define an anonymous implementation of the interface (but that is verbose). Either way, Kotlin-Java interop will automatically switch between Unit/Void.
Assuming the interface in Java is named MyJavaCallback, here are two ways to do SAM conversion:
fun myKotlinMethod(onError: (Throwable) -> Unit) { // Can not change here
val onErrorJavaCompatible = MyJavaCallback { t -> onError(t) }
MyJavaLibrary.aMethod(onErrorJavaCompatible)
}
fun myKotlinMethod(onError: (Throwable) -> Unit) {
MyJavaLibrary.aMethod { t -> onError(t) }
}
Since you are directly passing through the code, you can prevent an unnecessary wrapper object around the Kotlin function by using inline:
inline fun myKotlinMethod(crossinline onError: (Throwable) -> Unit) {
MyJavaLibrary.aMethod { t -> onError(t) }
}
One more step you can take for simpler code. When passing a function as a function, you don't need to put it in a lambda and invoke it:
inline fun myKotlinMethod(crossinline onError: (Throwable) -> Unit) {
MyJavaLibrary.aMethod(onError)
}
Just to be complete, here's how you would do it the verbose way, assuming the function in the Java interface is named onSomeEvent:
inline fun myKotlinMethod(crossinline onError: (Throwable) -> Unit) {
val onErrorJavaCompatible = object: MyJavaCallback {
override fun onSomeEvent(t: Throwable) {
onError(t)
}
}
MyJavaLibrary.aMethod(onErrorJavaCompatible)
}
I have an authorizaton Aspect that checks specific conditions based on method annotation.
This specific example shows annotation to mark a method that is only accessible by customer service. But unfortunately this isn't the only condition.
We have to confirm the customerServiceId that is also passed as one of method parameters. The parameter containing customerServiceId is pretty nested, so I was wondering if it's possible to get parameter value by some kind of a "path".
So let's say we have this method:
fun updateRemoteConfig(val remoteConfig: RemoteConfig) { doSomething() }
RemoteConfig class is pretty nested, so the path to customerServiceId would be something like: remoteConfig.customerService.id
What I would like to achieve is mark the method with annotation:
#CustomerServiceAccess(customerServiceIdPath = "remoteConfig.customerService.id")
And the value would then be fetched inside Aspect method. But I have no idea how to get to the specified value by path. Is it even possible?
The unknown is where arrows are in the code. Here's rest of the aspect:
#Aspect
class AuthorizationAspect {
#Pointcut("#annotation(com.my.project.annotations.CustomerServiceAccess)")
fun customerServiceAccess() = Unit
#Before("customerServiceAccess()")
fun checkAccess(joinPoint: JoinPoint) {
val methodSignature = joinPoint.signature as MethodSignature
val method = methodSignature.method
val canAccess = mutableListOf<() -> Boolean>()
.apply {
addAll(method.getAnnotationsByType(CustomerServiceAccess::class.java).map { it.canAccess(method) })
}
.any { it() }
if (!canAccess) {
throw UnauthorizedException(message = "User cannot perform this action")
}
}
private fun CustomerServiceAccess.canAccess(val method: Method): () -> Boolean = {
->> val customerServiceIdParam = method.getParameterByPath(getCustomerServiceIdPath())
SecurityContext.isCustomerService && SecurityContext.customerServiceId == customerServiceIdParam
}
private fun CustomerServiceAccess.getCustomerServiceIdPath(): String = this.customerServiceIdPath
}
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class CustomerServiceAccess(val customerServiceIdPath: String)
I have unit test in which I am trying to check is a use case is called with the right parameters but I get an error
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.xx.xxx.clean.orderview.domain.OnStandUseCaseCoroutine$Params.<init>, parameter serviceType
#Test
fun `when notifyOnStand is called then we create a TimestampedAction with the correct user id, vehicle, timestamp and pass that to the usecase`() {
val actionCaptor = argumentCaptor<TimestampedAction>()
val timestamp = DateTime.now()
every(noServiceRequiredBus.get()).thenReturn(Observable.just(REQUESTED))
every(timingsUpdater.timestampCalculator(any(), any())).thenReturn(timestamp)
baseOrderViewPresenter.setView(view)
baseOrderViewPresenter.notifyOnStand()
runBlocking {
verify(onStandUseCaseCoroutine).run(OnStandUseCaseCoroutine.Params(any(), any(), capture(actionCaptor)))
}
}
Use case which will get called when when called baseOrderViewPresenter.notifyOnStand() from tets case
class OnStandUseCaseCoroutine #Inject constructor(
private val orderRepository: OrderRepository,
private val serviceOrderTypeProvider: ServiceOrderTypeProvider
) : UseCaseCoroutine<GenericResponse, OnStandUseCaseCoroutine.Params> (){
override suspend fun run(params: Params) =
orderRepository.notifyOnStandSuspend(serviceOrderTypeProvider.apiPathFor(params.serviceType), params.id, params.action)
data class Params(val serviceType: String, val id: String, val action: TimestampedAction)
}
Presenter layer which has the call to use case
private fun onstandUseCaseCoroutines(serviceType: String, id: String, action: TimestampedAction, callback: (GenericResponse?) -> Unit) {
try {
onStandUseCaseCoroutine(OnStandUseCaseCoroutine.Params(serviceType, id, action)) {
callback.invoke(it)
}
} catch (exception: Exception) {
onStandResponseErrors()
}
}
how can I fix this please
I tried changing to bellow code but that did not fix it, I am not sure what to do the capture(actionCaptor) bit if that is the issue
runBlocking {
verify(onStandUseCaseCoroutine).run(OnStandUseCaseCoroutine.Params(anyString(), anyString(), capture(actionCaptor)))
}
Any suggestions please
Thanks
R
I'm trying to write some unit-test cases for a class with functions that has callbacks as arguments (Please see code below)
class NetworkApi(private val url: String) {
fun getToken(listener: (String) -> Unit) {
Thread(Runnable {
Thread.sleep(2000)
if (TextUtils.isEmpty(url)) listener("")
else listener("Dummy token")
}).start()
}
}
and the unit test case is
#RunWith(AndroidJUnit4::class)
class NetworkApiTest {
var networkApi: NetworkApi? = null
#Test
fun testEmptyToken() {
networkApi = NetworkApi("")
networkApi?.getToken {
Assert.assertThat(it, isEmptyOrNullString())
}
}
}
And whenever I run this test case, I do get success all the time, no matter what values I send. I know that I'm not doing exact way.
Can someone please help me writing unit test cases for classes in JUnit.
The problem is that the test finishes before the callback is invoked and the assert is in the wrong thread.
You have to copy the result from the callback back to the main thread. Use a CompletableFuture.
If you like to fail the test after a period of time you can use the get method with a timeout value:
#RunWith(AndroidJUnit4::class)
class NetworkApiTest {
var networkApi: NetworkApi? = null
#Test
fun testEmptyToken() {
val future = CompletableFuture<String>()
networkApi = NetworkApi("")
networkApi?.getToken {
future.complete(it)
}
val result = future.get(3,TimeUnit.SECONDS)
Assert.assertThat(it, isEmptyOrNullString())
}
}
Given the following class (written in kotlin):
class Target {
fun <R> target(filter: String, mapper: (String) -> R): R = mapper(filter)
}
I'm able to test in java, the test code:
#Test
public void testInJava() {
Target mockTarget = Mockito.mock(Target.class);
Mockito.when(mockTarget.target(
argThat(it -> true),
Mockito.argThat(it -> true)
)).thenReturn(100);
assert mockTarget.target("Hello World", it -> 1) == 100;
}
The java test pass as expected, but the same test is written in kotlin:
#Test
fun test() {
val mockTarget = Mockito.mock(Target::class.java)
Mockito.`when`(mockTarget.target(
Mockito.argThat<String> { true },
mapper = Mockito.argThat<Function1<String, Int>>({ true }))
).thenReturn(100)
assert(mockTarget.target("Hello World") { 1 } == 100)
}
The kotlin version I receive the following exception:
java.lang.IllegalStateException: Mockito.argThat<String> { true } must not be null
Why is it happening and how can I test that using kotlin?
I also faced the same problem.
And finally, I found argThat() will return null, and normally the argument in the function in kotlin, does not accept null type.
The source code of argThat from ArgumentMatchers.java
public static <T> T argThat(ArgumentMatcher<T> matcher) {
reportMatcher(matcher);
return null;
}
You can see that it return null. So when we mock the function, it will throw IllegalStateException, because argThat returns null and argument can't be null.
It mean that if your function is:
fun doSomething(arg1: String): Int {
// do something
}
When you mock it like that:
Mockito.`when`(
doSomething(Mockito.argThat<String> { true })
).thenReturn(100)
It will throw IllegalStateException
So you should change your function like that:
fun doSomething(arg1: String?): Int {
// do something
}
Change the "String" to "String?", make it accept null type.
My solution is to define the argument with class? so that it can accept null, but I don't know if it is a great solution
In 2022, Mockito-Kotlin officially solves the problem.
The fix is very simple: Just import the argThat/eq/... from the mockito-kotlin package, instead of the mockito package, and everything is done!
Related: https://github.com/mockito/mockito-kotlin/wiki/Mocking-and-verifying
As of this writing, mockito-kotlin hasn't been updated for more than a year. As with all of these libraries, there's always a constant need for keeping them up-to-date, and I didn't want to get stuck with an unmaintained library.
So I came up with another way to solve the null issue with argThat without using any other libraries.
Say we've an interface UuidRepository as follows:
interface UuidRepository {
suspend fun Entity save(entity: Entity): Entity
}
class Entity has two properties, userId: String and uuid: String.
The following code fails:
Mockito.verify(uuidRepository).save(argThat { it.userId == someValue && it.uuid == "test" })
with the error:
argThat { it.userId == someValue && it.uuid == "test" } must not be null
To solve this, we get all the invocation on the mock and then verify the ones we want:
val invocations = Mockito.mockingDetails(uuidRepository).invocations
.filter { setOf("findById", "save").contains(it.method.name) }
.map { it.method.name to it.arguments }
.toMap()
assertThat(invocations).containsKey("save")
val first = invocations["save"]?.first()
assertThat(first).isNotNull
val entity = first as Entity
assertThat(entity.userId).isEqualTo(someValue)
assertThat(entity.uuid).isEqualTo("test")