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}"
}
Related
What is wrong with my configuration or code ?
I have this error highlighted
Cannot resolve method 'plant(timber.log.Timber.DebugTree)'
for the code
import timber.log.Timber;
public class AppClass extends Application {
#Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); }
}
}
but it builds and it executes. Still I think it means something, no ?
Configuration infos:
Android Studio Bumblebee | 2021.1.1
classpath 'com.android.tools.build:gradle:7.1.0'
Gradle: com.jakewharton.timber:timber:5.0.1#aar
ext.kotlin_version = '1.6.10'
sourceCompatibility JavaVersion.VERSION_1_8
Until issue fixed (as #n8yn8 noted in question comment) I solved it with downgrade to version 4.7.1:
implementation 'com.jakewharton.timber:timber:4.7.1'
In app level build.gradle file, set the following jakewharton timber version:
implementation 'com.jakewharton.timber:timber:4.7.1'
Then in your application class onCreate() Method:
For Kotlin:
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
} else {
Timber.plant(ReleaseTree())
}
For Java:
if (BuildConfig.DEBUG) {
Timber.plant(new DebugTree());
} else {
Timber.plant(new ReleaseTree());
}
Inner ReleaseTree() class Kotlin:
inner class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
Inner ReleaseTree() class Java:
class ReleaseTree extends Timber.Tree {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
For the workaround solution without downgrade dependency version and also no need to apply with another dependency by keep applying the one from JakeWharton, we can try to config Timber in Kotlin instead of Java class since the warning message only appear on Java class.
By doing so, you can try two options bellow:
Convert your custom application class from Java to Kotlin
Create another class in Kotlin and create new method to config Timber with sample bellow:
TimberUtils.kt
import timber.log.Timber
object TimberUtils {
#JvmStatic
fun configTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
YourCustomJavaClass.java
#Override
public void onCreate() {
super.onCreate();
TimberUtils.configTimber();
}
Hope it can resolve your problem.
For those using sentry-timber
Just use
implementation "io.sentry:sentry-android:$sentry_version"
implementation "io.sentry:sentry-android-timber:$sentry_version"
Remove this dependency
implementation "com.jakewharton.timber:timber:$timber_version"
For me, this fix resolves the issue
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
I try to mock some methods in the project so that when they are called, a certain value is returned.
But when you run the tests, they fall with the output:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers! 0 matchers expected, 1 recorded:
-> at com.hodzi.stackviewer.questions.detail.QuestionDetailPresenterTest.voteTest(QuestionDetailPresenterTest.kt:69)
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"));
If you run the same code in debug mode and run through all the lines, then when you call shared.getToken (), the value that we specified is returned. But with normal startup, tests fall on this line.
Code:
import com.hodzi.stackviewer.questions.QuestionsInteractor
import com.hodzi.stackviewer.utils.Shared
import com.hodzi.stackviewer.utils.Vote
import org.junit.BeforeClass
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
internal class QuestionDetailPresenterTest {
companion object {
lateinit var presenter: QuestionDetailPresenter
lateinit var view: QuestionDetailView
#BeforeClass #JvmStatic
fun setUp() {
val questionsInteractor: QuestionsInteractor =
Mockito.mock(QuestionsInteractor::class.java)
val shared: Shared =
Mockito.mock(Shared::class.java)
Mockito.`when`(shared.getToken()).thenReturn("23")
// Mockito.doReturn("23").`when`(shared).getToken()
view = Mockito.mock(QuestionDetailView::class.java)
presenter = QuestionDetailPresenter(questionsInteractor, shared)
}
}
#Test
fun voteTest() {
presenter.vote(ArgumentMatchers.anyInt(), Vote.QUESTION_DOWN)
Mockito.verify(view).goToAuth()
}
}
Shared:
interface Shared {
companion object {
const val KEY_TOKEN: String = "keyToken"
}
fun getToken(): String
fun saveToken(token: String?)
}
Presenter:
class QuestionDetailPresenter(val questionsInteractor: QuestionsInteractor, val shared: Shared) :
BasePresenter<QuestionDetailView>() {
lateinit var question: Question
fun vote(id: Int, vote: Vote) {
print(vote)
if (Strings.isEmptyString(shared.getToken())) {
view?.goToAuth()
return
}
val observable: Observable<out Data> = when (vote) {
Vote.ANSWER_UP -> {
questionsInteractor.answerUpVote(id, shared.getToken())
}
Vote.ANSWER_DOWN -> {
questionsInteractor.answerDownVote(id, shared.getToken())
}
Vote.QUESTION_UP -> {
questionsInteractor.questionUpVote(id, shared.getToken())
}
Vote.QUESTION_DOWN -> {
questionsInteractor.questionDownVote(id, shared.getToken())
}
}
baseObservableData(observable,
{ data ->
run {
Log.d(Const.LOG_TAG, "success")
}
},
{ throwable ->
run {
Log.d(Const.LOG_TAG, "error")
}
}
)
}
}
Thanks!
There's nothing wrong with your mocking of shared, I think the problem is with:
presenter.vote(ArgumentMatchers.anyInt(), Vote.QUESTION_DOWN)
Just use a real Int instead of the ArgumentMatchers.anyInt().
Like
presenter.vote(0, Vote.QUESTION_DOWN)
Matchers are used when matching arguments on a mocked object, for example
val calulator = (mock with Mockito)
when(calculator.divideByTwo(anyInt()).thenReturn(1)
would mean calculator.divideByTwo(int: Int) returns 1 when called with any Int.
When calling methods of real objects to test them (like you do with your presenter), you use real parameters.
Gradle sets the build to failed when unit test failed, but set BUILD SUCCESSFUL when instrumented test fails(espresso in my case) anybody know how to force build to fail when espresso failed?
Ops: need this to integrate with Jenkins, so it must failed when unit test and espresso test failed and don't want to use Jenkins Text-finder plugin.
Just to let everyone know, I founded a solution, following the answer:android-gradle-plugin 1.2.2: Flag ignoreFailures causes error in task connectedAndroidTest , I did
project.gradle.taskGraph.whenReady {
connectedAndroidTest[flavor]Debug {
ignoreFailures = false
}
connectedAndroidTest[flavor2]Debug {
ignoreFailures = false
}
}
and execute ./gradlew connectedAndroidTest --continue
now it runs all instrumented test for both flavours and if there is a failure in any one of them, the build failure as well.
I found more clean solution, use in root build.gradle:
//if in start command "--continue" was added, then apply ignoring for android test fails
gradle.taskGraph.whenReady { graph ->
if (gradle.startParameter.continueOnFailure) {
graph.allTasks.findAll { it.name ==~ /connected.*AndroidTest/ }*.ignoreFailures = true
}
}
This solution works for me.
Add this code to UI Test class:
// Use this TestRule to setup IdlingResource
#Rule
#JvmField
val mIdlingResourceTestRule = CustomIdlingResTestRule()
This is code of CustomIdlingResTestRule:
import androidx.test.espresso.IdlingPolicies
import androidx.test.espresso.IdlingRegistry
import com.jakewharton.espresso.OkHttp3IdlingResource
import com.kasikorn.retail.mbanking.kplus.basemodule.AppModule
import com.kasikorn.retail.mbanking.kplus.util.OkHttpProvider
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.util.concurrent.TimeUnit
/**
* TestRule class help to setup IdlingResource
*/
class CustomIdlingResTestRule : TestRule {
protected var mIdlingResource: CustomIdlingResource = CustomIdlingResource()
val idlingResourceOkHttp: OkHttp3IdlingResource by lazy {
OkHttp3IdlingResource.create(AppModule.APP_CONTEXT, OkHttpProvider.getOkHttpInstance())
}
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
IdlingPolicies.setMasterPolicyTimeout(60, TimeUnit.SECONDS)
IdlingPolicies.setIdlingResourceTimeout(26, TimeUnit.SECONDS)
IdlingRegistry.getInstance().register(idlingResourceOkHttp, mIdlingResource)
//NOTE: use try cache to ignore UI Test Failures
try {
base?.evaluate()
} catch (e: Throwable) {
e.printStackTrace()
}
IdlingRegistry.getInstance().unregister(idlingResourceOkHttp, mIdlingResource)
}
}
}
}
import android.app.Activity
import android.util.Log
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.core.internal.deps.guava.collect.Iterables
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
class CustomIdlingResource() : IdlingResource {
private var mIsIdle = false
private var resourceCallback: IdlingResource.ResourceCallback? = null
override fun getName(): String = this.javaClass.simpleName
override fun isIdleNow(): Boolean {
if (mIsIdle) return true
val currentActivity = getCurrentActivity()
Log.d("LOG", this.javaClass.simpleName + " | isIdleNow() | currentActivity: $currentActivity")
if (currentActivity != null) {
mIsIdle = true
resourceCallback?.onTransitionToIdle()
}
return mIsIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
this.resourceCallback = callback
}
private fun getCurrentActivity(): Activity? {
val activity = arrayOfNulls<Activity>(1)
val activities: Collection<Activity> = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
activity[0] = Iterables.getOnlyElement(activities)
return activity[0]
}
}
Need help to write a unit test for the below code using Mockito and JUnit4,
public class MyFragmentPresenterImpl {
public Boolean isValid(String value) {
return !(TextUtils.isEmpty(value));
}
}
I tried below method:
MyFragmentPresenter mMyFragmentPresenter
#Before
public void setup(){
mMyFragmentPresenter=new MyFragmentPresenterImpl();
}
#Test
public void testEmptyValue() throws Exception {
String value=null;
assertFalse(mMyFragmentPresenter.isValid(value));
}
but it returns following exception,
java.lang.RuntimeException: Method isEmpty in android.text.TextUtils
not mocked. See http://g.co/androidstudio/not-mocked for details. at
android.text.TextUtils.isEmpty(TextUtils.java) at ....
Because of JUnit TestCase class cannot use Android related APIs, we have to Mock it.
Use PowerMockito to Mock the static class.
Add two lines above your test case class,
#RunWith(PowerMockRunner.class)
#PrepareForTest(TextUtils.class)
public class YourTest
{
}
And the setup code
#Before
public void setup() {
PowerMockito.mockStatic(TextUtils.class);
PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(new Answer<Boolean>() {
#Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
CharSequence a = (CharSequence) invocation.getArguments()[0];
return !(a != null && a.length() > 0);
}
});
}
That implement TextUtils.isEmpty() with our own logic.
Also, add dependencies in app.gradle files.
testCompile "org.powermock:powermock-module-junit4:1.6.2"
testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
testCompile "org.powermock:powermock-api-mockito:1.6.2"
testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
Thanks Behelit's and Exception's answer.
Use PowerMockito
Add this above your class name, and include any other CUT class names (classes under test)
#RunWith(PowerMockRunner.class)
#PrepareForTest({TextUtils.class})
public class ContactUtilsTest
{
Add this to your #Before
#Before
public void setup(){
PowerMockito.mockStatic(TextUtils.class);
mMyFragmentPresenter=new MyFragmentPresenterImpl();
}
This will make PowerMockito return default values for methods within TextUtils
You would also have to add the relevant gradle depedencies
testCompile "org.powermock:powermock-module-junit4:1.6.2"
testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
testCompile "org.powermock:powermock-api-mockito:1.6.2"
testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
This is a known issue as mentioned by #Exception. In my case, I also stumbled upon same situation but on advice of senior dev decided to use Strings.isNullOrEmpty() instead of TextUtils.isEmpty(). It turned out to be a nice way to avoid it.
Update: I should better mention it that this utility function Strings.isNullOrEmpty() requires Guava library.
This is a known issue, due to a clause in Testing Fundamental of Android which says:
You can use the JUnit TestCase class to do unit testing on a class
that does not call Android APIs.
The default behavior is problematic when using classes like Log or TextUtils.
To sum up:
android.jar is mock before, so some Android API return value may not be as expected.
JUnit itself is a single measure for the java code, so try not to use the Android API methods.
Source: http://www.liangfeizc.com/2016/01/28/unit-test-on-android/
add this line in your gradle file in case of Android Studio.
android{
....
testOptions {
unitTests.returnDefaultValues = true
}
}
You should use Robolectric:
testImplementation "org.robolectric:robolectric:3.4.2"
And then
#RunWith(RobolectricTestRunner::class)
class TestClass {
...
}
Solution 1:
I would like to provide a Kotlin and a Java version.
Kotlin version:
import android.text.TextUtils
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Matchers.any
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
#RunWith(PowerMockRunner::class)
#PrepareForTest(TextUtils::class)
class UserOwnedDataTest1 {
#Before
fun setup() {
PowerMockito.mockStatic(TextUtils::class.java)
PowerMockito.`when`(TextUtils.isEmpty(any(CharSequence::class.java))).thenAnswer { invocation ->
val a = invocation.arguments[0] as? CharSequence
a?.isEmpty() ?: true
}
}
}
Java version:
import android.text.TextUtils;
import org.junit.Before;
import org.junit.runner.RunWith;
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.Matchers.any;
#RunWith(PowerMockRunner.class)
#PrepareForTest(TextUtils.class)
public final class UserOwnedDataTest2 {
#Before
public void setup() {
PowerMockito.mockStatic(TextUtils.class);
PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer((Answer<Boolean>) invocation -> {
CharSequence a = (CharSequence) invocation.getArguments()[0];
return !(a != null && a.length() > 0);
});
}
}
Do not forget to add the dependencies:
testCompile "org.powermock:powermock-module-junit4:1.6.2"
testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
testCompile "org.powermock:powermock-api-mockito:1.6.2"
testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
I remember that we still need another dependency, but not clearly.
Anyway you could fix the missing dependency easily.
Solution 2:
Or you could add the same package and class name with TextUtils
package android.text;
public class TextUtils {
public static boolean isEmpty( CharSequence str) {
return str == null || str.length() == 0;
}
}
I was able to solve this error by running the test class with the following.
#RunWith(RobolectricGradleTestRunner.class)
public class MySimpleTest {
.....a bunch of test cases
}
This wiki page explains in greater detail
https://github.com/yahoo/squidb/wiki/Unit-testing-with-model-objects
I replaces everywhere in my project TextUtils.isEmpty(...) with this:
/**
* Util class to be used instead of Android classes for Junit tests.
*/
public class Utils {
/**
* Returns true if the string is null or 0-length.
* #param str the string to be examined
* #return true if str is null or zero length
*/
public static boolean isEmpty(#Nullable CharSequence str) {
return str == null || str.length() == 0;
}
}
As a followup to Johnny's answer, to catch TextUtils.isEmpty(null) calls as well, you could use this piece of code.
PowerMockito.mockStatic(TextUtils.class);
PowerMockito.when(TextUtils.isEmpty(any()))
.thenAnswer((Answer<Boolean>) invocation -> {
Object s = invocation.getArguments()[0];
return s == null || s.length() == 0;
});
With newer power mock (2.0.9) and mockito (3.9.0) I had. to change execution to this one:
when(TextUtils.isEmpty(any())).thenAnswer((Answer<Boolean>) invocation -> {
CharSequence a = (CharSequence) invocation.getArguments()[0];
return a == null || a.length() == 0;
});