117

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)
Sasikumar Murugesan
  • 4,412
  • 10
  • 51
  • 74
user3762991
  • 1,431
  • 3
  • 11
  • 16

15 Answers15

224

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...
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Paglian
  • 6,162
  • 3
  • 26
  • 20
  • 32
    This is bloody brilliant. And it dodges the need for PowerMockito. 10/10 – Sipty Oct 30 '17 at 18:06
  • 1
    Good Answer, My theory is If you have to use Mock APIs in Unit Testing then your code is not organized enough to be unit testable. If you are using outside libraries then use Integration tests with a runtime and real objects. In all my Android Apps I have created a wrapper class LogUtil that enables logs based on a flag, this helps me avoid mocking Log class and enable/disable logs with a flag. In production I remove all logs statements with progaurd anyway. – MG Developer Apr 13 '18 at 01:16
  • 4
    @MGDevelopert you're right. IMO this technique/trick should be used scarcely. For instance, I only do that for the `Log` class because is too ubiquitous and passing a Log wrapper everywhere makes the code less readable. In most cases dependency injection should be used instead. – Paglian Apr 16 '18 at 12:08
  • 7
    Works good. Just before you copy-paste it, add package name: **package android.util;** – Michał Dobi Dobrzański Apr 17 '18 at 10:13
  • Is it possible to adapt this approach for Kotlin? I tried putting it into an object and got an IncompatibleClassChangeError exception. – Big McLargeHuge Dec 12 '18 at 21:46
  • 1
    @DavidKennedy use `@file:JvmName("Log")` and top-level functions. – Miha_x64 Feb 05 '19 at 19:18
  • Add isLogger inside Log class as well if you are facing it with aws android sdk – UzumakiL Oct 17 '19 at 18:21
  • 1
    Another way to fix this for Kotlin is to annotate all mocked methods with `@JvmStatic`. – Martin Melka Apr 10 '20 at 10:05
  • What a nice solution!! – AndyB Jul 09 '20 at 13:59
  • 1
    not the solution we deserve, but the one we need right now. – chugadie Sep 22 '20 at 11:49
  • Nice solution. Why do you need to return int 0 though? – Ishmael7 Feb 02 '22 at 11:22
  • Why does this not collide with ordinary Log class, or Robolectric's stub implementation? – JohnyTex May 10 '22 at 11:33
  • 1
    Worked for me, but only as Java file. Conversion to Kotlin class causes calssdef exceptions – k1ngarthur Aug 16 '22 at 10:51
  • @JohnyTex Java allows classes with the same qualified name (package + class name) to be in the classpath multiple times, but only the first one will be loaded and used (the classpath is just a bunch of folder and/or file paths). The classpath used for tests is constructured in such a way that your classes are always at the front, so the one from Roboelectric has no chance of ever being loaded. – wujek Aug 09 '23 at 17:11
  • 1
    @Ishmael7 It doesn't have to be 0, but you have to return an int because it is what the original method returns - this is the number of bytes written. Why Log.e and others return this is beyond me. – wujek Aug 09 '23 at 18:41
77

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
Greg Ennis
  • 14,917
  • 2
  • 69
  • 74
  • Great introducing +1, tests are passed but the error is already reports! – MHSaffari Nov 04 '19 at 07:40
  • 2
    If you want to capture Log.w add: `every { Log.w(any(), any()) } returns 0` – MrK Jul 24 '20 at 15:10
  • does not seem to work with `Log.wtf` (`every { Log.wtf(any(), any()) } returns 0`): compilation faills with error: `Unresolved reference: wtf`. IDE lint says nothing in code. Any idea ? – Mackovich Aug 18 '20 at 14:51
  • 2
    Brilliant +1 !!... This worked for me while using Mockk. – RKS Sep 14 '20 at 12:11
  • 2
    Can I use mockk to make calls to `Log.*` use `println()` to output the intended Log? – Emil S. Nov 06 '20 at 13:24
58

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.

jjz
  • 2,007
  • 3
  • 21
  • 30
IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147
  • 37
    *From Docs:* **Caution:** Setting the returnDefaultValues property to true should be done with care. The null/zero return values can introduce regressions in your tests, which are hard to debug and might allow failing tests to pass. **Only use it as a last resort.** – Manish Kumar Sharma Jun 12 '17 at 11:34
  • 1
    The unitTests variable name has changed to `isReturnDefaultValues`: https://developer.android.com/reference/tools/gradle-api/4.1/com/android/build/api/dsl/UnitTestOptions#isreturndefaultvalues – robotsquidward Nov 11 '21 at 13:37
  • @robotsquidward `unitTests.isReturnDefaultValues = true` generates a build error: *"Could not set unknown property 'isReturnDefaultValues' for object of type com.android.build.gradle.internal.dsl.TestOptions$UnitTestOptions. "* – WebViewer Jan 09 '23 at 07:26
27

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

plátano plomo
  • 1,672
  • 1
  • 18
  • 26
16

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.

Abel
  • 688
  • 6
  • 19
8

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

    }
 }
Payal Kothari
  • 109
  • 2
  • 5
  • 1
    It worth mention that due to a bug, for JUnit 4.12, use PowerMock >= 1.6.1. Otherwise, try to run with JUnit 4.11 – manasouza Jun 08 '19 at 00:52
6

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
}
5

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.

Tosin John
  • 494
  • 5
  • 15
4

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
        }
    }
}
HenriqueMS
  • 3,864
  • 2
  • 30
  • 39
3

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 ...
}
michael-slx
  • 665
  • 9
  • 15
Lipi
  • 332
  • 2
  • 8
  • No idea why this isn't the accepted answer. If you're testing Android code, you're not going to mock every single Android call. That is not practical. You use Robolectric as described above. Robolectric provides implementations of the Android SDK. It can be challenging at times exactly what to expect from Robolectric's implementation but it is manageable. – Jeffrey Blattman Aug 20 '21 at 18:57
2

Mockito doesn't mock static methods. Use PowerMockito on top. Here is an example.

Community
  • 1
  • 1
Antiohia
  • 1,142
  • 13
  • 37
  • 1
    @user3762991 Also you need to change your matchers. You cannot use a matcher in the `thenReturn(...)` statement. You need to specify a tangible value. See more info [here](https://stackoverflow.com/questions/36771597/mockito-throwing-invaliduseofmatchersexception) – troig Apr 22 '16 at 08:10
  • If e,d,v method cannot be mocked, just because of this limitation does mockito become unusable? – user3762991 Apr 22 '16 at 12:15
  • 2
    If you cannot eat a fork, does it become unusable? It simply has another purpose. – Antiohia Apr 25 '16 at 05:47
1

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.

Bawender Yandra
  • 200
  • 1
  • 9
0

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;

...
}
user0904
  • 210
  • 3
  • 7
0

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}"
}
Yingding Wang
  • 450
  • 3
  • 14
-1

This is my Log class:

package av.android.util
    
import java.text.SimpleDateFormat
import java.util.*
    
class Log {
    companion object {
        private val shortTimeFormat by lazy { SimpleDateFormat("HH:mm:ss.SSS", Locale.ITALY) }

        fun v(tag: String, msg: String) {
            log("$tag: $msg", LogLevel.VERBOSE)
        }

        fun d(tag: String, msg: String) {
            log("$tag: $msg", LogLevel.DEBUG)
        }

        fun i(tag: String, msg: String) {
            log("$tag: $msg", LogLevel.INFO)
        }

        fun w(tag: String, msg: String) {
            log("$tag: $msg", LogLevel.WARNING)
        }

        fun e(tag: String, msg: String? = null, thr: Throwable? = null) {
            val message = { msg ?: "" }
            log("$tag: $message, ${thr?.printStackTrace()}", LogLevel.ERROR)
        }

        private fun log(msg: String, logLevel: LogLevel = LogLevel.EMPTY) {
            val time = shortTimeFormat.format(Date())
            println("$time ${logLevel.initial} [${Thread.currentThread().name}] $msg")
        }
    }

    enum class LogLevel(val initial: String) {
        VERBOSE("V"),
        DEBUG("D"),
        INFO("I"),
        WARNING("W"),
        ERROR("E"),
        EMPTY("")
    }    
}

And this is how I use it:

@Test
fun main() = runBlocking {
    Log.v(TAG, "_START_")
    launch {
        for(i in 0..5) {
            delay(1000L)
            Log.d(TAG, "$i) World!")
        }
    }
    for(i in 0..5) {
        Log.d(TAG, "$i) Hello")
    }
    Log.v(TAG, "_END_")
}

I hope it can be useful.

vitiello.antonio
  • 323
  • 3
  • 12