如何在日志中模拟方法 e

这里要测试的类是 Utils.java,下面是在 UtilsTest 类中调用的方法。 即使我正在模仿 Log.e 方法,如下所示

 @Before
public void setUp() {
when(Log.e(any(String.class),any(String.class))).thenReturn(any(Integer.class));
utils = spy(new Utils());
}

我得到了下面的例外

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)
53079 次浏览

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

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

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


}
}

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.

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

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

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.

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

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;


...
}

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.

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

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