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)
user3762991
quelle

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

IgorGanapolsky
quelle
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
Greg Ennis
quelle
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().

plátano plomo
quelle
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(){

    }
 }
Payal Kothari
quelle
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
}
kosiara - Bartosz Kosarzycki
quelle
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.

Tosin John
quelle
2

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

Antiohia
quelle
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.

Abel
quelle
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 ...
}
Lipi
quelle
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
quelle
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
quelle