Mocking static classes with Powermock2 and Kotlin - android

in Android I've been using the version 1.6.1 of Powermock and all this implementation worked really good for statics.
It's not working now at all when I changed to 2.0.0-beta.5. Indeed, it didn't even work upgrading from my previous 1.6.1 to 1.7.1.
I have this implementation:
// Power Mockito
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4-rule-agent:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
//testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'
// Mockito
testImplementation "org.mockito:mockito-core:2.11.0"
testImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"
androidTestImplementation("com.nhaarman:mockito-kotlin-kt1.1:1.5.0", {
exclude group: 'org.mockito', module: 'mockito-core'
})
androidTestImplementation 'org.mockito:mockito-android:2.11.0'
and I'm trying to mock a static the same way I was doing with 1.6.1:
#RunWith(PowerMockRunner::class)
#PrepareForTest(SharedPreferencesHelper.Companion::class, ConfigUseCaseTests::class)
class ConfigUseCaseTests {
lateinit var context: Context
#Before
fun setUp() {
context = mock()
}
#Test
fun getConfigs_fromJson() {
PowerMockito.mockStatic(SharedPreferencesHelper.Companion::class.java)
val instance = mock<SharedPreferencesHelper.Companion>()
doReturn("foo")
.whenever(instance)
.loadString(isA(), anyString(), anyString(), anyString())
// whenever(instance.loadString(isA(), anyString(), anyString(), anyString())).thenReturn("foo") // This shows the same error
PowerMockito.whenNew(SharedPreferencesHelper.Companion::class.java)
.withAnyArguments()
.thenReturn(instance)
val mockedFoo = instance.loadString(context, "", "", "") // This shows "foo"
val mockedCompanion = SharedPreferencesHelper.loadString(context, "", "", "") // This is throwing NullPointerException
Assert.assertEquals(mockedCompanion, "foo")
}
}
My SharedPreferencesHelper looks like:
class SharedPreferencesHelper {
companion object {
#Suppress("NON_FINAL_MEMBER_IN_OBJECT")
open fun loadString(context: Context, fileName: String, key: String, defaultValue: String?): String? {
val sharedPreferences = getWithFileName(context, fileName)
return if (!sharedPreferences.contains(key)) {
defaultValue
} else {
sharedPreferences.getString(key, defaultValue)
}
}
}
}
I've tried to play with the open but it didn't work.
Exception: (I don't understand it at all)
java.lang.NullPointerException
at my.package.ConfigUseCaseTests.getConfigs_fromJson(ConfigUseCaseTests.kt:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:326)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
I can say, sometimes IT WORKS!! I'm adding the video because it looks amazing that it happens just sometimes:
https://youtu.be/YZObVLcERBo (watch at the middle and the end)

The way companion objects are created on compilation is by creating a static field inside the surrounding class. It get's instantiated on the static scope (before the test is instantiated).
This is how it looks when decompiled in Java:
public final class SharedPreferencesHelper {
public static final SharedPreferencesHelper.Companion Companion = new
SharedPreferencesHelper.Companion((DefaultConstructorMarker)null);
// ...
}
For this to work you'll have to assign the given field with your mock instead of intercepting the creation of the Companion object. This doesn't even require to use PowerMock and can be done with reflexion: https://dzone.com/articles/how-to-change-private-static-final-fields

Related

Robolectric start a fragment that has an observer

How to start a fragment with a LiveData observer in the test scope with Robolectric
Fragment
class MyFragment(private val viewModel: MyViewModel) : Fragment() {
...
fun myObserver {
...
// If I remove this observer the test will pass.
viewModel.MyLiveData.observe(viewLifecycleOwner, Observer{
...
}
}
}
My Test uses the RobolectricTestRunner so I can launch the fragment in the test scope.
#RunWith(robolectricTestRunner::class)
class MyFragmentTest {
// Executes tasks in the Architecture Components in the same thread
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Test
fun testOne() {
val viewModel: MyViewModel = mock(MyViewModel::class.java)
val scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
fragmentArgs = null
themeResId = R.style.Theme_MyTheme
)
// Tried implementing shadowOf as the error suggests.
}
}
I get the following error when trying to run the test. I've tried setting Main looper to idle before and after instantiating the FragmentScenario.
java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.
I've tried the following
implementing a shadow class for the Main Looper. Annotating the class with Looper mode.
#RunWith(RobolectricTestRunner::class)
#LooperMode(LooperMode.Mode.PAUSED)
class MyFragmentTest {
Adding scenario states
scenario.moveToState(Lifecycle.State.CREATED)
scenario.moveToState(Lifecycle.State.RESUMED)
My Test dependencies.
// Test
testImplementation 'androidx.arch.core:core-testing:2.1.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3'
testImplementation "androidx.test.ext:junit-ktx:1.1.2"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3"
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation "org.robolectric:robolectric:4.5.1"
testImplementation "org.mockito:mockito-android:2.28.2"
// Testing Fragments
debugImplementation "androidx.fragment:fragment-testing:1.3.2"
Links I've used to find a solution'
Testing LiveData Transformations?
https://jeroenmols.com/blog/2019/01/17/livedatajunit5/
I took a look into your repository on github here. Here's what I've found.
Problem 1
Your first problem is that you mock out a ViewModel. So, when you simulate onResume for your Fragment it invokes:
fun liveDataObserver() {
viewModel.scoreLiveData.observe(viewLifecycleOwner, {
//
} )
}
Since viewModel is mocked, scoreLiveData is null and you get an NPE.
To fix this, you also mock out scoreLiveData method so that it returns some acceptable result:
...
val liveData = MutableLiveData<Int>().apply { value = 3 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
...
This will fix your testOne completely, but not yet testTwo.
Problem 2
It's related only to your testTwo method. The problem is that you're calling liveDataObserver() in your also block, and that is invoked before your Fragment's viewLifecycleOwner has been set in onCreateView:
...
scenario = launchFragmentInContainer {
MyFragment(viewModel).also {
it.liveDataObserver()
}
}
...
I'm not sure what exactly you're trying to test here, but if you want to verify that you can start observing after Fragment's View has been created, you could do something as following:
...
// make sure your Fragment is started
scenario = launchFragmentInContainer (
factory = MainFragmentFactory(viewModel),
initialState = Lifecycle.State.STARTED
)
// call liveDataObserver on it
scenario.withFragment {
this.liveDataObserver()
}
Full Code
#RunWith(RobolectricTestRunner::class)
class MyFragmentTest {
private lateinit var scenario: FragmentScenario<MyFragment>
#Test
fun testOne() = runBlockingTest {
val liveData = MutableLiveData<Int>().apply { value = 1 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
fragmentArgs = null,
themeResId = R.style.Theme_TDDScoreKeeper,
initialState = Lifecycle.State.STARTED
)
scenario.moveToState(Lifecycle.State.RESUMED)
scenario.recreate() // Simulates if the phone ran low on resources and the app had to be recreated.
}
#Test
fun testTwo() {
val liveData = MutableLiveData<Int>().apply { value = 1 }
val viewModel = mock(MyViewModel::class.java)
doReturn(liveData).`when`(viewModel).scoreLiveData
scenario = launchFragmentInContainer(
factory = MainFragmentFactory(viewModel),
initialState = Lifecycle.State.STARTED
)
scenario.withFragment {
this.liveDataObserver()
}
}
}

Android instrumented test freezes when it tests a suspend function that uses RoomDatabase.withTransaction

I'm trying to test the following LocalDataSource function, NameLocalData.methodThatFreezes function, but it freezes. How can I solve this? Or How can I test it in another way?
Class to be tested
class NameLocalData(private val roomDatabase: RoomDatabase) : NameLocalDataSource {
override suspend fun methodThatFreezes(someParameter: Something): Something {
roomDatabase.withTransaction {
try {
// calling room DAO methods here
} catch(e: SQLiteConstraintException) {
// ...
}
return something
}
}
}
Test class
#MediumTest
#RunWith(AndroidJUnit4::class)
class NameLocalDataTest {
private lateinit var nameLocalData: NameLocalData
// creates a Room database in memory
#get:Rule
var roomDatabaseRule = RoomDatabaseRule()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setup() = runBlockingTest {
initializesSomeData()
nameLocalData = NameLocalData(roomDatabaseRule.db)
}
#Test
fun methodThatFreezes() = runBlockingTest {
nameLocalData.methodThatFreezes // test freezes
}
// ... others NameLocalDataTest tests where those functions been tested does not use
// roomDatabase.withTransaction { }
}
Gradle's files configuration
espresso_version = '3.2.0'
kotlin_coroutines_version = '1.3.3'
room_version = '2.2.5'
test_arch_core_testing = '2.1.0'
test_ext_junit_version = '1.1.1'
test_roboletric = '4.3.1'
test_runner_version = '1.2.0'
androidTestImplementation "androidx.arch.core:core-testing:$test_arch_core_testing"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.ext:junit:$test_ext_junit_version"
androidTestImplementation "androidx.test:rules:$test_runner_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
Last time I wrote a test for Room database I just simply use runBlock and it worked for me...
Could you take a look into this sample and check if it works for you as well?
Edit:
Ops! I missed this part... I tried this (in the same sample):
I defined a dummy function in my DAO using #Transaction
#Transaction
suspend fun quickInsert(book: Book) {
save(book)
delete(book)
}
I think this is the key of the problem. Add setTransactionExecutor to your Database instantiation.
appDatabase = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
AppDatabase::class.java
).setTransactionExecutor(Executors.newSingleThreadExecutor())
.build()
Finally, the test worked using runBlocking
#Test
fun dummyTest() = runBlocking {
val dao = appDatabase.bookDao();
val id = dummyBook.id
dao.quickInsert(dummyBook)
val book = dao.bookById(id).first()
assertNull(book)
}
See this question.
I had tried many things to make this work, used runBlockingTest, used TestCoroutineScope, tried runBlocking, used allowMainThreadQueries, setTransactionExecutor, and setQueryExecutor on my in memory database.
But nothing worked until I found this comment thread in the Threading models in Coroutines and Android SQLite API article in the Android Developers Medium blog, other people mentioned running into this. Author Daniel Santiago said:
I’m not sure what Robolectric might be doing under the hood that could cause withTransaction to never return.
We usually don’t have Robolectric tests but we have plenty of Android Test examples if you want to try that route: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
I was able to fix my test by changing it from a Robolectric test to an AndroidTest and by using runBlocking
This is an example from the google source:
#Before
#Throws(Exception::class)
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
TestDatabase::class.java
)
.build()
booksDao = database.booksDao()
}
#Test
fun runSuspendingTransaction() {
runBlocking {
database.withTransaction {
booksDao.insertPublisherSuspend(
TestUtil.PUBLISHER.publisherId,
TestUtil.PUBLISHER.name
)
booksDao.insertBookSuspend(TestUtil.BOOK_1.copy(salesCnt = 0))
booksDao.insertBookSuspend(TestUtil.BOOK_2)
booksDao.deleteUnsoldBooks()
}
assertThat(booksDao.getBooksSuspend())
.isEqualTo(listOf(TestUtil.BOOK_2))
}
}

Mockito.verify didn't see method exectution, even if it was

The error I have:
The code with the error:
#RunWith(PowerMockRunner::class)
#PrepareForTest(PotatoProvider::class, PotatoConsumer::class)
class WantedButNotInvoked {
#Mock
lateinit var potatoConsumer: PotatoConsumer
#Test
fun potato() {
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
//verify(potatoConsumer).accept(any()) //-> This fails too with the same reason
}
}
data class Potato(val value: Int = 1)
class PotatoConsumer : Consumer<Potato> {
override fun accept(t: Potato?) {
println(t)
}
}
So I making subscribe with this mock(potatoConsumer), and the rxJava have called 'accept', and mockito mark it as interaction, but mockito thinks this interaction is not what I'm expecting, why?
Versions of libraries used her:
mockitoVersion = '2.8.9'
mockitoAndroidVersion = '2.7.22'
powerMockVersion="2.0.2"
kotlinMockito="2.1.0"
rxKotlin = "2.3.0"
rxJavaVersion = "2.2.10"
Kinda workaround
Some fields mocked by powermock, fails on 'verify', but fields mocked with mockito is not;
Mockito can't mock not opened fields, without mock-maker-inline, but mockito conflicts with Powermock mock-maker-inline;
Powermock can delegate calls of mock-maker-inline to other mock-maker-inline(https://github.com/powermock/powermock/wiki/PowerMock-Configuration)
Use Mockito.mock on the failed fields instead of #Mock/Powermock mock injection
Example of the "green" potato test method using PowerMockRunner
#Test
fun potato() {
potatoConsumer = mock() // <-
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(potato)
}
I am not familiar with PowerMock but I tried this test and it passes:
#Test
fun potato() {
fakePotatoProvider = Mockito.mock(PotatoProvider::class.java)
potatoConsumer = Mockito.mock(PotatoConsumer::class.java)
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(Potato()))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
}
Maybe because you aren't passing the same instance of Potato(). Try to refactor your code to this
#Test
fun potato() {
val testPotato = Potato()
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(testPotato))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(testPotato)
}
As I mentioned above, the reason why it might be failing is the constant creation of new instances when passing your Potato object, hance that comparison fails.

How to mock method e in Log

Here Utils.java is my class to be tested and following is the method which is called in UtilsTest class.
Even if I am mocking Log.e method as shown below
#Before
public void setUp() {
when(Log.e(any(String.class),any(String.class))).thenReturn(any(Integer.class));
utils = spy(new Utils());
}
I am getting the following exception
java.lang.RuntimeException: Method e in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.util.Log.e(Log.java)
at com.xxx.demo.utils.UtilsTest.setUp(UtilsTest.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
This worked out for me. I'm only using JUnit and I was able to mock up the Log class without any third party lib very easy. Just create a file Log.java inside app/src/test/java/android/util with contents:
package android.util;
public class Log {
public static int d(String tag, String msg) {
System.out.println("DEBUG: " + tag + ": " + msg);
return 0;
}
public static int i(String tag, String msg) {
System.out.println("INFO: " + tag + ": " + msg);
return 0;
}
public static int w(String tag, String msg) {
System.out.println("WARN: " + tag + ": " + msg);
return 0;
}
public static int e(String tag, String msg) {
System.out.println("ERROR: " + tag + ": " + msg);
return 0;
}
// add other methods if required...
}
If using Kotlin I would recommend using a modern library like mockk which has built-in handling for statics and many other things. Then it can be done with this:
mockkStatic(Log::class)
every { Log.v(any(), any()) } returns 0
every { Log.d(any(), any()) } returns 0
every { Log.i(any(), any()) } returns 0
every { Log.e(any(), any()) } returns 0
You can put this into your gradle script:
android {
...
testOptions {
unitTests.returnDefaultValues = true
}
}
That will decide whether unmocked methods from android.jar should throw exceptions or return default values.
Using PowerMockito:
#RunWith(PowerMockRunner.class)
#PrepareForTest({Log.class})
public class TestsToRun() {
#Test
public void test() {
PowerMockito.mockStatic(Log.class);
}
}
And you're good to go. Be advised that PowerMockito will not automatically mock inherited static methods, so if you want to mock a custom logging class that extends Log, you must still mock Log for calls such as MyCustomLog.e().
Thanks to #Paglian answer and #Miha_x64 comment, I was able to make the same thing work for kotlin.
Add the following Log.kt file in app/src/test/java/android/util
#file:JvmName("Log")
package android.util
fun e(tag: String, msg: String, t: Throwable): Int {
println("ERROR: $tag: $msg")
return 0
}
fun e(tag: String, msg: String): Int {
println("ERROR: $tag: $msg")
return 0
}
fun w(tag: String, msg: String): Int {
println("WARN: $tag: $msg")
return 0
}
// add other functions if required...
And voilà, your calls to Log.xxx should call theses functions instead.
Use PowerMockito.
#RunWith(PowerMockRunner.class)
#PrepareForTest({ClassNameOnWhichTestsAreWritten.class , Log.class})
public class TestsOnClass() {
#Before
public void setup() {
PowerMockito.mockStatic(Log.class);
}
#Test
public void Test_1(){
}
#Test
public void Test_2(){
}
}
Using PowerMock one can mock Log.i/e/w static methods from Android logger. Of course ideally you should create a logging interface or a facade and provide a way of logging to different sources.
This is a complete solution in Kotlin:
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
/**
* Logger Unit tests
*/
#RunWith(PowerMockRunner::class)
#PrepareForTest(Log::class)
class McLogTest {
#Before
fun beforeTest() {
PowerMockito.mockStatic(Log::class.java)
Mockito.`when`(Log.i(any(), any())).then {
println(it.arguments[1] as String)
1
}
}
#Test
fun logInfo() {
Log.i("TAG1,", "This is a samle info log content -> 123")
}
}
remember to add dependencies in gradle:
dependencies {
testImplementation "junit:junit:4.12"
testImplementation "org.mockito:mockito-core:2.15.0"
testImplementation "io.kotlintest:kotlintest:2.0.7"
testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'
testImplementation 'org.powermock:powermock-core:2.0.0-beta.5'
testImplementation 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
}
To mock Log.println method use:
Mockito.`when`(Log.println(anyInt(), any(), any())).then {
println(it.arguments[2] as String)
1
}
I would recommend using timber for your logging.
Though it will not log anything when running tests but it doesn't fail your tests unnecessarily the way android Log class does. Timber gives you a lot of convenient control over both debug and production build of you app.
The kotlin version of #Paglian 's answer, no need to mock android.util.Log for JUnit tests :)
Emphasis:
1 -> the package name at the top
2 -> the annotation on top of the functions
package android.util
class Log {
companion object {
fun d(tag: String, msg: String): Int {
println("DEBUG: $tag: $msg")
return 0
}
#JvmStatic
fun i(tag: String, msg: String): Int {
println("INFO: $tag: $msg")
return 0
}
#JvmStatic
fun w(tag: String, msg: String): Int {
println("WARN: $tag: $msg")
return 0
}
#JvmStatic
fun w(tag: String, msg: String, exception: Throwable): Int {
println("WARN: $tag: $msg , $exception")
return 0
}
#JvmStatic
fun e(tag: String, msg: String): Int {
println("ERROR: $tag: $msg")
return 0
}
}
}
Another solution is to use Robolectric. If you want to try it, check its setup.
In your module's build.gradle, add the following
testImplementation "org.robolectric:robolectric:3.8"
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
And in your test class,
#RunWith(RobolectricTestRunner.class)
public class SandwichTest {
#Before
public void setUp() {
}
}
In newer versions of Robolectric (tested with 4.3) your test class should look as follows:
#RunWith(RobolectricTestRunner.class)
#Config(shadows = ShadowLog.class)
public class SandwichTest {
#Before
public void setUp() {
ShadowLog.setupLogging();
}
// tests ...
}
Mockito doesn't mock static methods. Use PowerMockito on top. Here is an example.
If you are using any mocking library then you can mock this function to internally use the kotlin standard library's print function to print the log to the console.
Here is an example using MockK library in Koltin.
mockkStatic(Log::class)
every { Log.i(any(), any()) } answers {
println(arg<String>(1))
0
}
The above is illustrating of mocking the Log.i function. You can create separate variants for the e,d,w,v functions.
If your are using the org.slf4j.Logger, then just mocking the Logger in test class using PowerMockito worked for me.
#RunWith(PowerMockRunner.class)
public class MyClassTest {
#Mock
Logger mockedLOG;
...
}
Extending the answer from kosiara for using PowerMock and Mockito in Java with JDK11 to mock the android.Log.v method with System.out.println for unit testing in Android Studio 4.0.1.
This is a complete solution in Java:
import android.util.Log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.ArgumentMatchers.any;
#RunWith(PowerMockRunner.class)
#PrepareForTest(Log.class)
public class MyLogUnitTest {
#Before
public void setup() {
// mock static Log.v call with System.out.println
PowerMockito.mockStatic(Log.class);
Mockito.when(Log.v(any(), any())).then(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
String TAG = (String) invocation.getArguments()[0];
String msg = (String) invocation.getArguments()[1];
System.out.println(String.format("V/%s: %s", TAG, msg));
return null;
}
});
}
#Test
public void logV() {
Log.v("MainActivity", "onCreate() called!");
}
}
Remember to add dependencies in your module build.gradle file where your unit test exists:
dependencies {
...
/* PowerMock android.Log for OpenJDK11 */
def mockitoVersion = "3.5.7"
def powerMockVersion = "2.0.7"
// optional libs -- Mockito framework
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
// optional libs -- power mock
testImplementation "org.powermock:powermock-module-junit4:${powerMockVersion}"
testImplementation "org.powermock:powermock-api-mockito2:${powerMockVersion}"
testImplementation "org.powermock:powermock-module-junit4-rule:${powerMockVersion}"
testImplementation "org.powermock:powermock-module-junit4-ruleagent:${powerMockVersion}"
}

kotlin and ArgumentCaptor - IllegalStateException

I have a problem with capturing the Class argument via ArgumentCaptor. My test class looks like this:
#RunWith(RobolectricGradleTestRunner::class)
#Config(sdk = intArrayOf(21), constants = BuildConfig::class)
class MyViewModelTest {
#Mock
lateinit var activityHandlerMock: IActivityHandler;
#Captor
lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>>
#Captor
lateinit var booleanCaptor: ArgumentCaptor<Boolean>
private var objectUnderTest: MyViewModel? = null
#Before
fun setUp() {
initMocks(this)
...
objectUnderTest = MyViewModel(...)
}
#Test
fun thatNavigatesToAddListScreenOnAddClicked(){
//given
//when
objectUnderTest?.addNewList()
//then
verify(activityHandlerMock).navigateTo(classCaptor.capture(), booleanCaptor.capture())
var clazz = classCaptor.value
assertNotNull(clazz);
assertFalse(booleanCaptor.value);
}
}
When I run the test, following exception is thrown:
java.lang.IllegalStateException: classCaptor.capture() must not be null
Is it possible to use argument captors in kotlin?
=========
UPDATE 1:
Kotlin: 1.0.0-beta-4584
Mockito: 1.10.19
Robolectric: 3.0
=========
UPDATE 2:
Stacktrace:
java.lang.IllegalStateException: classCaptor.capture() must not be null
at com.example.view.model.ShoplistsViewModelTest.thatNavigatesToAddListScreenOnAddClicked(ShoplistsViewModelTest.kt:92)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
From this blog
"Getting matchers to work with Kotlin can be a problem. If you have a method written in kotlin that does not take a nullable parameter then we cannot match with it using Mockito.any(). This is because it can return void and this is not assignable to a non-nullable parameter. If the method being matched is written in Java then I think that it will work as all Java objects are implicitly nullable."
A wrapper function is needed that returns ArgumentCaptor.capture() as nullable type.
Add the following as a helper method to your test
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
Please see, MockitoKotlinHelpers.kt provided by Google in the Android Architecture repo for reference. the capture function provides a convenient way to call ArgumentCaptor.capture(). Call
verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))
Update: If the above solution does not work for you, please check Roberto Leinardi's solution in the comments below.
The return value of classCaptor.capture() is null, but the signature of IActivityHandler#navigateTo(Class, Boolean) does not allow a null argument.
The mockito-kotlin library provides supporting functions to solve this problem.
Code should be:
#Captor
lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>>
#Captor
lateinit var booleanCaptor: ArgumentCaptor<Boolean>
...
#Test
fun thatNavigatesToAddListScreenOnAddClicked(){
//given
//when
objectUnderTest?.addNewList()
//then
verify(activityHandlerMock).navigateTo(
com.nhaarman.mockitokotlin2.capture<Class<BaseActivity>>(classCaptor.capture()),
com.nhaarman.mockitokotlin2.capture<Boolean>(booleanCaptor.capture())
)
var clazzValue = classCaptor.value
assertNotNull(clazzValue);
val booleanValue = booleanCaptor.value
assertFalse(booleanValue);
}
OR
var classCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Class<BaseActivity>>()
var booleanCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Boolean>()
...
verify(activityHandlerMock).navigateTo(
classCaptor.capture(),
booleanCaptor.capture()
)
also in build.gradle add this:
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
Use kotlin-mockito https://mvnrepository.com/artifact/com.nhaarman/mockito-kotlin/1.5.0 as dependency and sample code as written below :
argumentCaptor<Hotel>().apply {
verify(hotelSaveService).save(capture())
assertThat(allValues.size).isEqualTo(1)
assertThat(firstValue.name).isEqualTo("İstanbul Hotel")
assertThat(firstValue.totalRoomCount).isEqualTo(10000L)
assertThat(firstValue.freeRoomCount).isEqualTo(5000L)
}
As stated by CoolMind in the comment, you first need to add gradle import for Kotlin-Mockito and then shift all your imports to use this library. Your imports will now look like:
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.isNull
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
Then your test class will be something like this:
val mArgumentCaptor = argumentCaptor<SignUpInteractor.Callback>()
#Test
fun signUp_success() {
val customer = Customer().apply {
name = "Test Name"
email = "test#example.com"
phone = "0123444456789"
phoneDdi = "+92"
phoneNumber = ""
countryCode = "92"
password = "123456"
}
mPresenter.signUp(customer)
verify(mView).showProgress()
verify(mInteractor).createAccount(any(), isNull(), mArgumentCaptor.capture())
}
According this solution my solution here:
fun <T> uninitialized(): T = null as T
//open verificator
val verificator = verify(activityHandlerMock)
//capture (would be same with all matchers)
classCaptor.capture()
booleanCaptor.capture()
//hack
verificator.navigateTo(uninitialized(), uninitialized())
Came here after the kotlin-Mockito library didn't help.
I created a solution using reflection.
It is a function which extracts the argument provided to the mocked-object earlier:
fun <T: Any, S> getTheArgOfUsedFunctionInMockObject(mockedObject: Any, function: (T) -> S, clsOfArgument: Class<T>): T{
val argCaptor= ArgumentCaptor.forClass(clsOfArgument)
val ver = verify(mockedObject)
argCaptor.capture()
ver.javaClass.methods.first { it.name == function.reflect()!!.name }.invoke(ver, uninitialized())
return argCaptor.value
}
private fun <T> uninitialized(): T = null as T
Usage:
(Say I have mocked my repository and tested a viewModel. After calling the viewModel's "update()" method with a MenuObject object, I want to make sure that the MenuObject actually called upon the repository's "updateMenuObject()" method:
viewModel.update(menuObjectToUpdate)
val arg = getTheArgOfUsedFunctionInMockObject(mockedRepo, mockedRepo::updateMenuObject, MenuObject::class.java)
assertEquals(menuObjectToUpdate, arg)
You can write a wrapper over argument captor
class CaptorWrapper<T:Any>(private val captor:ArgumentCaptor<T>, private val obj:T){
fun capture():T{
captor.capture()
return obj
}
fun captor():ArgumentCaptor<T>{
return captor
}
}
Another approach:
/**
* Use instead of ArgumentMatcher.argThat(matcher: ArgumentMatcher<T>)
*/
fun <T> safeArgThat(matcher: ArgumentMatcher<T>): T {
ThreadSafeMockingProgress.mockingProgress().argumentMatcherStorage
.reportMatcher(matcher)
return uninitialized()
}
#Suppress("UNCHECKED_CAST")
private fun <T> uninitialized(): T = null as T
Usage:
verify(spiedElement, times(1)).method(
safeArgThat(
CustomMatcher()
)
)
If none of the fine solutions presented worked for you, here is one more way to try. It's based on Mockito-Kotlin.
[app/build.gradle]
dependencies {
...
testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0'
}
Define Rule and Mock in your test file.
#RunWith(AndroidJUnit4::class)
class MockitoTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var mockList: MutableList<String>
And here is an example.
#Test
fun `argument captor`() {
mockList.add("one")
mockList.add("two")
argumentCaptor<String>().apply {
// Verify that "add()" is called twice, and capture the arguments.
verify(mockList, times(2)).add(capture())
assertEquals(2, allValues.size)
assertEquals("one", firstValue)
assertEquals("two", secondValue)
}
}
}
Alternatively, you can also use #Captor as well.
#Captor
private lateinit var argumentCaptor: ArgumentCaptor<String>
#Test
fun `argument captor`() {
mockList.add("one")
mockList.add("two")
verify(mockList, times(2)).add(capture(argumentCaptor))
assertEquals(2, argumentCaptor.allValues.size)
assertEquals("one", argumentCaptor.firstValue)
assertEquals("two", argumentCaptor.secondValue)
}

Categories

Resources