Wie man Methode e im Protokoll verspottet


80

Hier ist Utils.java meine zu testende Klasse und die folgende Methode wird in der UtilsTest-Klasse aufgerufen. Auch wenn ich mich über die unten gezeigte Log.e-Methode lustig mache

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

Ich erhalte die folgende Ausnahme

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)

Antworten:


152

Das hat bei mir geklappt. Ich benutze nur JUnit und konnte die LogKlasse ohne Lib von Drittanbietern sehr einfach verspotten . Erstellen Sie einfach eine Datei Log.javainnerhalb app/src/test/java/android/utilmit dem Inhalt:

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

19
Das ist verdammt brillant. Und es weicht der Notwendigkeit von PowerMockito aus. 10/10
Sipty

1
Gute Antwort: Meine Theorie lautet: Wenn Sie Mock-APIs für Unit-Tests verwenden müssen, ist Ihr Code nicht so organisiert, dass er Unit-testbar ist. Wenn Sie externe Bibliotheken verwenden, verwenden Sie Integrationstests mit einer Laufzeit und realen Objekten. In all meinen Android Apps habe ich eine Wrapper-Klasse LogUtil erstellt, die Protokolle basierend auf einem Flag aktiviert. Dies hilft mir, das Verspotten der Protokollklasse zu vermeiden und Protokolle mit einem Flag zu aktivieren / deaktivieren. In der Produktion entferne ich sowieso alle Protokollanweisungen mit progaurd.
MG Entwickler

4
@MGDevelopert du hast recht. IMO sollte diese Technik / dieser Trick kaum angewendet werden. Zum Beispiel mache ich das nur für die LogKlasse, weil es zu allgegenwärtig ist und das Übergeben eines Log-Wrappers überall den Code weniger lesbar macht. In den meisten Fällen sollte stattdessen die Abhängigkeitsinjektion verwendet werden.
Paglian

5
Funktioniert gut. Fügen
Michał Dobi Dobrzański

1
@ DavidKennedy verwenden @file:JvmName("Log")und Funktionen der obersten Ebene.
Miha_x64

39

Sie können dies in Ihr Gradle-Skript einfügen:

android {
   ...
   testOptions { 
       unitTests.returnDefaultValues = true
   }
}

Dadurch wird entschieden, ob nicht gesockelte Methoden aus android.jar Ausnahmen auslösen oder Standardwerte zurückgeben sollen.


25
Aus Dokumenten : Achtung: Das Setzen der Eigenschaft returnDefaultValues ​​auf true sollte mit Vorsicht erfolgen. Die Null / Null-Rückgabewerte können zu Regressionen in Ihren Tests führen, die schwer zu debuggen sind und möglicherweise das Bestehen fehlgeschlagener Tests ermöglichen. Verwenden Sie es nur als letzten Ausweg.
Manish Kumar Sharma

29

Wenn Sie Kotlin verwenden, würde ich die Verwendung einer modernen Bibliothek wie mockk empfehlen, die über eine integrierte Handhabung für Statik und viele andere Dinge verfügt. Dann kann es damit gemacht werden:

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

Great introducing +1, tests are passed but the error is already reports!
MHSFisher

1
If you want to capture Log.w add: every { Log.w(any(), any<String>()) } returns 0
MrK

does not seem to work with Log.wtf (every { Log.wtf(any(), any<String>()) } returns 0): compilation faills with error: Unresolved reference: wtf. IDE lint says nothing in code. Any idea ?
Mackovich

Brilliant +1 !!... This worked for me while using Mockk.
RKS

Can I use mockk to make calls to Log.* use println() to output the intended Log?
Emil S.

26

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


1
How did you get PowerMockRunner in Gradle??
IgorGanapolsky

4
@IgorGanapolsky See my answer here.
plátano plomo

Check out my answer in Kotlin here for mocking Log.e and Log.println
kosiara - Bartosz Kosarzycki

Is PowerMockito still a popular solution in 2019 for Kotiln? Or should we look at other mock libraries (i.e. MockK).
IgorGanapolsky

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

    }
 }

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

5

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
}

Is that somehow possible in Java as well?
Bowi

@Bowi: see my solution mock Log.v with system.out.println in Java which also works with JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang

4

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.


2

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


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
troig

If e,d,v method cannot be mocked, just because of this limitation does mockito become unusable?
user3762991

2
If you cannot eat a fork, does it become unusable? It simply has another purpose.
Antiohia

2

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.


1

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

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;

...
}

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}"
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.