Unsure how to unit test interface in Kotlin with chained block - android

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.

Related

Using Mockk to mock private function that takes a lambda

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

Unable to use lambda arrow expression for Livedata observe method in Kotlin

I am new to kotlin and I am a little confused while using lambda expression in LiveData observe method.
The signature for observe method is as follows
observe(LifecycleOwner owner, Observer<? super T> observer)
where Observer is an interface with a single method
void onChanged (T t)
However,calling the above observe method in kotlin as follows gives type mismatch error :
val myViewModel = ViewModelProviders.of(this).get(AnimeListViewModel::class.java)
myViewModel.animes.observe(this, { anime -> println(anime) })
Isn't this the same as calling the setOnClickListener on a view. The following piece of code works perfectly without any compilation error:
val myView = View(this)
myView.setOnClickListener { view -> println(view) }
I have already read this answer which shows how to call the method using lambda expression (using SAM conversion). However, I am still not sure why a simple arrow expression would fail.
LiveData doesn't have a lambda expression, you should pass the observer interface as an object
myViewModel.animes.observe(this, Observer { anime -> println(anime) })
Or by creating an extension function like this
fun <T : Any> LiveData<T>.observe(lifecycleOwner: LifecycleOwner, block: (T) -> Unit) = observe(lifecycleOwner, Observer(block))
And calling it like this
myViewModel.animes.observe(this) { anime -> println(anime) }
Or like this
fun main() {
myViewModel.animes.observe(this, ::handleLiveData)
}
fun handleLiveData(anime: Anime) {
println(anime)
}
There are some problems on kotlin to resolve generics, so that's the reason. Kotlin has been working on this, and you will find the whole explanation
here.

Kotlin - Testing lambda using mockito

I am using lambda expression in my function, where i need to test my method using mockito.
class A {
fun testLambda(){
val obj = B()
obj.testMethod(1,{
number -> println(number)
})
}
}
class B {
fun testMethod(number : Int,action : (Int) -> Unit){
action(number+1)
}
}
I need to test these using mockito, i have no idea about this, any help ?

Verify suspend function with Matchers.anyObject()

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())
}
}

Mockito Wanted but not Invoked

I'm new to writing tests and using Mockito.
I've read the similar topics here on Stackoverflow and made the suggested changes, making sure that regarded classes / interfaces / methods are open.
I tried to follow this
Mocking the constructor injected dependencies
This is the test I came up with so far
class RegistrationPresenterTest {
#Test
fun testRegisterSuccess() {
val mockService = mock<IHerokuInteractor>()
val mockLocal = mock<ILocalStorageInteractor>()
val mockView = mock<RegisterView>()
val mockRegistrationResponse = HerokuRegisterResponse("hash")
val mockPair = ImeiPair("imei","hash")
val presenter = RegisterPresenterImpl(mockLocal,mockService)
whenever(mockService.register(any())).thenReturn(Observable.just(mockRegistrationResponse))
whenever(mockLocal.clearPreferences()).thenReturn(Observable.just(true))
whenever(mockLocal.putImeiPair(any())).thenReturn(Observable.just(true))
//whenever(presenter.writeImeiPairLocally(any())) How do I specify parameters since it uses a parameter from the register method?
presenter.bindView(mockView)
presenter.register("imei","male")
verify(mockService, times(1)).register(any())
verify(mockLocal,times(1)).clearPreferences()
verify(mockLocal,times(1)).putImeiPair(any())
verify(mockView,times(1)).moveToMain()
}
but the response I keep getting is
Wanted but not invoked:
registerPresenterImpl.writeImeiPairLocally(
<any com.company.appname.model.ImeiPair>
);
Actually, there were zero interactions with this mock.
I got this response even when I don't mention that method in the test.
This is my presenter register method. I've changed the classes / interfaces & methods involved to open (kotlin). I believe override methods are open by nature in kotlin.
open class RegisterPresenterImpl #Inject constructor(val localStorage : ILocalStorageInteractor, var herokuService : IHerokuInteractor)
override fun register(imei : String, gender : String){
subscription = herokuService.register(RegisterObject(imei,gender)).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{
registrationResult ->
Log.d(TAG,"${registrationResult}")
if(registrationResult.imei_hash != null){
writeImeiPairLocally(ImeiPair(imei,registrationResult.imei_hash))
}
else{
Log.e(TAG,"User already exists")
}
},
{
errorResponse -> Log.e(TAG,"Could not register user ${errorResponse.message}")
}
)
addSubscription(subscription)
}
and similarly the
open fun writeImeiPairLocally(pair : ImeiPair){
subscription = localStorage.clearPreferences().flatMap {
cleared -> localStorage.putImeiPair(pair)}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{
booleanResult -> view?.moveToMain()
},
{
errorResponse -> Log.e(TAG,"Could not write ImeiPair to SharedPreferences ${errorResponse.message}")
}
)
addSubscription(subscription)
}
Here is interfaces
open interface ILocalStorageInteractor : ILocalStorage{
fun getImeiPair() : Observable<ImeiPair>
fun putImeiPair(pair: ImeiPair) : Observable<Boolean>
}
open interface ILocalStorage {
fun clearPreferences() : Observable<Boolean>
}
All help is appreciated.
If you are using plain jUnit, then your AndroidSchedulers.mainThread() is null. That's why onNext is not called.
You need to override Schedulers in a setUp() method with:
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate(); // or .test()
}
});
To avoid concurrency in tests, I would recommend to override Schedulers.io() like this:
RxJavaHooks.setOnIOScheduler(scheduler1 -> Schedulers.immediate());
If you are going to use TestScheduler, don't forget to call TestScheduler.triggerActions() method.
Also don't forget to unregister Schedulers in tearDown() like this:
RxJavaHooks.reset();
RxAndroidPlugins.getInstance().reset();
AndroidSchedulers.reset();
Schedulers.reset();

Categories

Resources