Jak mockować metodę e w Log

81

Tutaj Utils.java jest moją klasą do przetestowania, a następującą metodą jest wywoływana w klasie UtilsTest. Nawet jeśli kpię z metody Log.e, jak pokazano poniżej

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

Otrzymuję następujący wyjątek

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
źródło

Odpowiedzi:

155

To mi się udało. Używam tylko JUnit i udało mi się bardzo łatwo wykonać makietę Logklasy bez biblioteki innej firmy . Po prostu utwórz plik w Log.javaśrodku app/src/test/java/android/utilz zawartością:

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
źródło
20
To jest cholernie genialne. I unika potrzeby PowerMockito. 10/10
Sipty
1
Dobra odpowiedź, moja teoria jest taka, że ​​jeśli musisz używać makietowych interfejsów API w testach jednostkowych, Twój kod nie jest wystarczająco zorganizowany, aby można go było testować jednostkowo. Jeśli korzystasz z bibliotek zewnętrznych, użyj testów integracji ze środowiskiem wykonawczym i rzeczywistymi obiektami. We wszystkich moich aplikacjach na Androida utworzyłem klasę opakowania LogUtil, która włącza dzienniki na podstawie flagi, co pomaga mi uniknąć fałszywego klasy Log i włączać / wyłączać dzienniki za pomocą flagi. W produkcji i tak usuwam wszystkie wyciągi logów za pomocą programu progaurd.
MG Developer
4
@MGDevelopert masz rację. IMO ta technika / sztuczka powinna być rzadko używana. Na przykład robię to tylko dla Logklasy, ponieważ jest zbyt wszechobecne, a przekazywanie opakowania dziennika wszędzie sprawia, że ​​kod jest mniej czytelny. W większości przypadków należy zamiast tego użyć iniekcji zależności.
Paglian
5
Działa dobrze. Tuż przed skopiowaniem i wklejeniem dodaj nazwę pakietu
Michał Dobi Dobrzański
1
@DavidKennedy korzysta @file:JvmName("Log")z funkcji najwyższego poziomu.
Miha_x64,
40

Możesz umieścić to w swoim skrypcie gradle:

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

To zadecyduje, czy niezmokowane metody z android.jar powinny zgłaszać wyjątki czy zwracać wartości domyślne.

IgorGanapolsky
źródło
26
From Docs: Caution: Przy ustawianiu właściwości returnDefaultValues ​​na wartość true należy zachować ostrożność. Zwracane wartości null / zero mogą powodować regresje w testach, które są trudne do debugowania i mogą pozwolić na zaliczenie testów zakończonych niepowodzeniem. Używaj go tylko w ostateczności.
Manish Kumar Sharma
31

Jeśli korzystasz z Kotlina, polecam korzystanie z nowoczesnej biblioteki, takiej jak mockk, która ma wbudowaną obsługę statyki i wielu innych rzeczy. Następnie można to zrobić za pomocą tego:

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
źródło
Świetne wprowadzenie +1, testy zdane, ale błąd jest już zgłoszony!
MHSFisher
1
Jeśli chcesz przechwycić Log.w dodaj:every { Log.w(any(), any<String>()) } returns 0
MrK
nie wydaje się do pracy z Log.wtf( every { Log.wtf(any(), any<String>()) } returns 0): faills kompilacji z powodu błędu: Unresolved reference: wtf. Lint IDE nic nie mówi w kodzie. Dowolny pomysł ?
Mackovich
Genialne +1 !! ... To zadziałało podczas używania Mockka.
RKS
Czy mogę używać mockk wykonywanie połączeń do Log.*użyć println()do wyprowadzenia przeznaczone Zaloguj?
Emil S.
26

Korzystanie z PowerMockito :

@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public class TestsToRun() {
    @Test
    public void test() {
        PowerMockito.mockStatic(Log.class);
    }
}

I jesteś gotowy. Należy pamiętać, że PowerMockito nie będzie automatycznie mockować dziedziczonych metod statycznych, więc jeśli chcesz mockować niestandardową klasę logowania, która rozszerza Log, nadal musisz mockować Log dla wywołań, takich jak MyCustomLog.e ().

plátano plomo
źródło
1
Jak zdobyłeś PowerMockRunner w Gradle?
IgorGanapolsky
4
@IgorGanapolsky Zobacz moją odpowiedź tutaj .
plátano plomo
Sprawdź moją odpowiedź w Kotlin tutaj dla wyśmianie Log.e i Log.println
kosiara - Bartosz Kosarzycki
Czy PowerMockito nadal jest popularnym rozwiązaniem w 2019 roku dla Kotiln? Albo powinniśmy spojrzeć na inne biblioteki pozorowane (np. MockK).
IgorGanapolsky
8

Użyj 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
źródło
1
Warto wspomnieć, że z powodu błędu, dla JUnit 4.12 użyj PowerMock> = 1.6.1. W przeciwnym razie spróbuj uruchomić z JUnit 4.11
manasouza,
5

Za pomocą PowerMockjednego można mockować metody statyczne Log.i / e / w z rejestratora systemu Android. Oczywiście najlepiej byłoby stworzyć interfejs logowania lub fasadę i zapewnić sposób logowania do różnych źródeł.

To kompletne rozwiązanie w Kotlinie:

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

pamiętaj, aby dodać zależności w 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'
}

Aby kpić z Log.printlnmetody użyj:

Mockito.`when`(Log.println(anyInt(), any(), any())).then {
    println(it.arguments[2] as String)
    1
}
kosiara - Bartosz Kosarzycki
źródło
Czy jest to w jakiś sposób możliwe również w Javie?
Bowi
@Bowi: zobacz moje rozwiązanie mock Log.v z system.out.println w Javie, które działa również z JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang
4

Polecam drewno do wycinki.

Chociaż nie rejestruje niczego podczas uruchamiania testów, ale nie zawodzi ich niepotrzebnie, tak jak robi to klasa Android Log. Timber zapewnia wygodną kontrolę zarówno nad debugowaniem, jak i produkcją aplikacji.

Tosin John
źródło
4

Dzięki odpowiedzi @Paglian i komentarzowi @ Miha_x64 mogłem sprawić, że to samo zadziała dla kotlina.

Dodaj następujący plik Log.kt w 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...

I voilà, twoje połączenia z Log.xxx powinny zamiast tego wywoływać te funkcje.

Abel
źródło
2

Mockito nie kpi z metod statycznych. Użyj PowerMockito na górze. Oto przykład.

Antiohia
źródło
1
@ user3762991 Musisz także zmienić dopasowania. Nie możesz użyć dopasowania w thenReturn(...)instrukcji. Musisz określić namacalną wartość. Zobacz więcej informacji tutaj
troig
Jeśli metody e, d, v nie można wyśmiać, to czy z powodu tego ograniczenia mockito staje się bezużyteczne?
user3762991
2
Jeśli nie możesz zjeść widelca, czy staje się bezużyteczny? Ma po prostu inny cel.
Antiohia
1

Innym rozwiązaniem jest użycie Robolectric. Jeśli chcesz to wypróbować, sprawdź jego konfigurację .

W pliku build.gradle modułu dodaj następujące elementy

testImplementation "org.robolectric:robolectric:3.8"

android {
  testOptions {
    unitTests {
      includeAndroidResources = true
    }
  }
}

A w klasie testowej

@RunWith(RobolectricTestRunner.class)
public class SandwichTest {
  @Before
  public void setUp() {
  }
}

W nowszych wersjach Robolectric (testowanych z wersją 4.3) Twoja klasa testowa powinna wyglądać następująco:

@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowLog.class)
public class SandwichTest {
    @Before
    public void setUp() {
        ShadowLog.setupLogging();
    }

    // tests ...
}
Lipi
źródło
0

Jeśli używasz org.slf4j.Logger, to po prostu mockowanie Loggera w klasie testowej przy użyciu PowerMockito zadziałało dla mnie.

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}
user0904
źródło
0

Przedłużenie odpowiedzi od kosiary za używanie PowerMock i Mockito w Javie z JDK11 w celu mockowaniaandroid.Log.v metody System.out.printlndo testów jednostkowych w Android Studio 4.0.1.

To jest kompletne rozwiązanie w Javie:

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!");
    }

}

Pamiętaj, aby dodać zależności w pliku build.gradle modułu , w którym istnieje test jednostkowy:

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
źródło