Как издеваться над методом e в журнале

81

Здесь 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)
user3762991
источник

Ответы:

155

Это сработало для меня. Я использую только JUnit, и мне очень легко удалось смоделировать Logкласс без какой-либо сторонней библиотеки . Просто создайте Log.javaвнутри файл app/src/test/java/android/utilс содержимым:

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
источник
20
Это чертовски здорово. И это позволяет избежать необходимости в PowerMockito. 10/10
Sipty
1
Хороший ответ. Моя теория заключается в том, что если вам нужно использовать Mock API в модульном тестировании, то ваш код недостаточно организован для модульного тестирования. Если вы используете сторонние библиотеки, используйте тесты интеграции со средой выполнения и реальными объектами. Во всех своих приложениях для Android я создал класс-оболочку LogUtil, который включает журналы на основе флага, это помогает мне избежать имитации класса журнала и включать / отключать журналы с помощью флага. В процессе производства я все равно удаляю все операторы журналов с помощью progaurd.
MG Developer
4
@MGDevelopert, ты прав. ИМО, этот метод / трюк вряд ли следует использовать. Например, я делаю это только для Logкласса, потому что он слишком распространен, и передача оболочки журнала повсюду делает код менее читаемым. В большинстве случаев вместо этого следует использовать внедрение зависимостей.
Paglian
5
Хорошо работает. Непосредственно перед копированием и вставкой добавьте имя пакета
Михал Доби Добжаньски
1
@DavidKennedy использует @file:JvmName("Log")и функции верхнего уровня.
Miha_x64 05
40

Вы можете поместить это в свой скрипт Gradle:

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

Это решит, должны ли разблокированные методы из android.jar вызывать исключения или возвращать значения по умолчанию.

Игорь Ганапольский
источник
26
Из Документов: Внимание! Установка для свойства returnDefaultValues ​​значения true должна выполняться с осторожностью. Нулевые / нулевые возвращаемые значения могут привести к регрессии в ваших тестах, которые трудно отлаживать и которые могут позволить пройти неудачные тесты. Используйте это только в крайнем случае.
Маниш Кумар Шарма,
31

Если вы используете Kotlin, я бы рекомендовал использовать современную библиотеку, такую ​​как mockk, которая имеет встроенную обработку статики и многих других вещей. Тогда это можно сделать так:

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
Грег Эннис
источник
Отличное введение +1, тесты пройдены но ошибка уже сообщает!
MHSFisher
1
Если вы хотите захватить Log.w, добавьте:every { Log.w(any(), any<String>()) } returns 0
MrK
не похоже на работу с Log.wtf( every { Log.wtf(any(), any<String>()) } returns 0): компиляции faills с ошибкой: Unresolved reference: wtf. IDE lint ничего не говорит в коде. Есть идеи ?
Mackovich
Великолепно +1 !! ... У меня это сработало при использовании Mockk.
RKS
Могу ли я использовать mockk делать вызовы Log.*использовать println()для вывода предназначен Войти?
Эмиль С.
26

Используя PowerMockito :

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

И тебе хорошо идти. Имейте в виду, что PowerMockito не будет автоматически имитировать унаследованные статические методы, поэтому, если вы хотите имитировать настраиваемый класс ведения журнала, расширяющий журнал, вы все равно должны имитировать журнал для таких вызовов, как MyCustomLog.e ().

Plátano Plomo
источник
1
Как вы получили PowerMockRunner в Gradle ??
Игорь Ганапольский
4
@IgorGanapolsky Смотрите мой ответ здесь .
Plátano Plomo
Посмотрите мой ответ на Котлине здесь для насмешек над Log.e и Log.println
kosiara - Bartosz Kosarzycki
PowerMockito по-прежнему остается популярным решением для Kotiln в 2019 году? Или нам стоит взглянуть на другие фиктивные библиотеки (например, MockK).
IgorGanapolsky
8

Используйте 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
Стоит упомянуть, что из-за ошибки для JUnit 4.12 используйте PowerMock> = 1.6.1. В противном случае попробуйте запустить JUnit 4.11
manasouza
5

С помощью логгера Android PowerMockможно имитировать статические методы Log.i / e / w . Конечно, в идеале вы должны создать интерфейс регистрации или фасад и предоставить способ регистрации к различным источникам.

Это полное решение на Котлине:

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

не забудьте добавить зависимости в 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'
}

Чтобы издеваться над Log.printlnметодом, используйте:

Mockito.`when`(Log.println(anyInt(), any(), any())).then {
    println(it.arguments[2] as String)
    1
}
Косиара - Бартош Косаржицкий
источник
Возможно ли это как-то и на Java?
Bowi
@Bowi: см. Мое решение, имитирующее Log.v с system.out.println на Java, которое также работает с JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang
4

Я бы рекомендовал использовать древесину для лесозаготовок.

Хотя он не будет ничего регистрировать при запуске тестов, но он не приведет к излишнему провалу ваших тестов, как это делает класс журнала Android. Timber дает вам много удобных средств управления как отладкой, так и производственной сборкой вашего приложения.

Тосин Джон
источник
4

Благодаря ответу @Paglian и комментарию @ Miha_x64 я смог заставить то же самое работать для kotlin.

Добавьте следующий файл Log.kt в 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...

И вуаля, ваши вызовы Log.xxx должны вместо этого вызывать эти функции.

Авель
источник
2

Mockito не издевается над статическими методами. Используйте PowerMockito поверх. Вот пример.

Антиохия
источник
1
@ user3762991 Также вам нужно изменить ваши сопоставители. Вы не можете использовать сопоставление в thenReturn(...)заявлении. Вам нужно указать материальную ценность. Больше информации здесь
troig
Если метод e, d, v не может быть издевательским, может ли mockito стать непригодным для использования только из-за этого ограничения?
user3762991
2
Если вы не можете съесть вилку, она приходит в негодность? У него просто другая цель.
Антиохия
1

Другое решение - использовать Robolectric. Если хотите попробовать, проверьте его настройку .

В вашем модуле build.gradle добавьте следующее

testImplementation "org.robolectric:robolectric:3.8"

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

И в вашем тестовом классе

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

В более новых версиях Robolectric (протестированных с 4.3) ваш тестовый класс должен выглядеть следующим образом:

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

    // tests ...
}
Липи
источник
0

Если вы используете org.slf4j.Logger, то просто издевательство над Logger в тестовом классе с помощью PowerMockito сработало для меня.

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}
user0904
источник
0

Расширение ответа от kosiara для использования PowerMock и Mockito в Java с JDK11 для имитацииandroid.Log.v метода с помощью System.out.printlnмодульного тестирования в Android Studio 4.0.1.

Это полное решение на 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!");
    }

}

Не забудьте добавить зависимости в файл build.gradle вашего модуля, в котором существует ваш модульный тест:

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}"
}
Индин Ван
источник