Cómo simular el método e en el registro

81

Aquí Utils.java es mi clase para ser probada y el siguiente es el método que se llama en la clase UtilsTest. Incluso si me estoy burlando del método Log.e como se muestra a continuación

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

Obtengo la siguiente excepción

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)
usuario3762991
fuente

Respuestas:

155

Esto funcionó para mí. Solo estoy usando JUnit y pude simular la Logclase sin ninguna biblioteca de terceros muy fácil. Simplemente crea un archivo Log.javadentro app/src/test/java/android/utilcon contenido:

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
fuente
20
Esto es increíblemente brillante. Y esquiva la necesidad de PowerMockito. 10/10
Sipty
1
Buena respuesta, mi teoría es que si tiene que usar API simuladas en pruebas unitarias, entonces su código no está lo suficientemente organizado como para ser comprobable por unidades. Si está utilizando bibliotecas externas, utilice pruebas de integración con un tiempo de ejecución y objetos reales. En todas mis aplicaciones de Android, he creado una clase contenedora LogUtil que habilita registros basados ​​en una bandera, esto me ayuda a evitar burlarme de la clase Log y habilitar / deshabilitar registros con una bandera. En producción, elimino todas las declaraciones de registros con progaurd de todos modos.
MG Developer
4
@MGDevelopert tienes razón. En mi opinión, esta técnica / truco debe usarse poco. Por ejemplo, solo hago eso para la Logclase porque es demasiado ubicuo y pasar un contenedor de registro en todas partes hace que el código sea menos legible. En la mayoría de los casos, se debe utilizar la inyección de dependencia.
Paglian
5
Funciona bien. Justo antes de copiarlo y pegarlo, agregue el nombre del paquete
Michał Dobi Dobrzański
1
@DavidKennedy usa @file:JvmName("Log")y funciones de nivel superior.
Miha_x64
40

Puedes poner esto en tu script gradle:

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

Eso decidirá si los métodos no modificados de android.jar deben generar excepciones o devolver valores predeterminados.

IgorGanapolsky
fuente
26
De Docs: Precaución: Establecer la propiedad returnDefaultValues ​​en true debe hacerse con cuidado. Los valores de retorno nulo / cero pueden introducir regresiones en sus pruebas, que son difíciles de depurar y pueden permitir que pasen las pruebas fallidas. Úselo solo como último recurso.
Manish Kumar Sharma
31

Si usa Kotlin, recomendaría usar una biblioteca moderna como mockk que tiene manejo incorporado para estática y muchas otras cosas. Entonces se puede hacer con esto:

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
fuente
Excelente introducción +1, las pruebas se aprobaron pero el error ya se informó.
MHSFisher
1
Si desea capturar Log.w agregue:every { Log.w(any(), any<String>()) } returns 0
MrK
no parece que el trabajo con Log.wtf( every { Log.wtf(any(), any<String>()) } returns 0): faills de compilación con el error: Unresolved reference: wtf. IDE lint no dice nada en el código. Alguna idea ?
Mackovich
Brillante +1 !! ... Esto funcionó para mí mientras usaba Mockk.
RKS
¿Puedo usar mockk para hacer llamadas para Log.*usar println()para generar el registro deseado?
Emil S.
26

Usando PowerMockito :

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

Y estás listo para irte. Tenga en cuenta que PowerMockito no simulará automáticamente los métodos estáticos heredados, por lo que si desea simular una clase de registro personalizada que amplíe Log, debe simular Log para llamadas como MyCustomLog.e ().

plátano plomo
fuente
1
¿Cómo conseguiste PowerMockRunner en Gradle?
IgorGanapolsky
4
@IgorGanapolsky Vea mi respuesta aquí .
plátano plomo
Mira mi respuesta en Kotlin aquí para burlarte de Log.e y Log.println
kosiara - Bartosz Kosarzycki
¿PowerMockito sigue siendo una solución popular en 2019 para Kotiln? O deberíamos mirar otras bibliotecas simuladas (es decir, MockK).
IgorGanapolsky
8

Utilice 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
fuente
1
Vale la pena mencionar que debido a un error, para JUnit 4.12, use PowerMock> = 1.6.1. De lo contrario, intente ejecutar con JUnit 4.11
manasouza
5

El uso de PowerMockuno puede simular los métodos estáticos Log.i / e / w del registrador de Android. Por supuesto, lo ideal sería crear una interfaz de registro o una fachada y proporcionar una forma de registro a diferentes fuentes.

Esta es una solución completa en 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")
    }
}

recuerde agregar dependencias en 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'
}

Para simular el Log.printlnuso del método:

Mockito.`when`(Log.println(anyInt(), any(), any())).then {
    println(it.arguments[2] as String)
    1
}
kosiara - Bartosz Kosarzycki
fuente
¿Es eso de alguna manera posible en Java también?
Bowi
@Bowi: vea mi solución simulando Log.v con system.out.println en Java, que también funciona con JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang
4

Recomendaría usar madera para su tala.

Aunque no registrará nada al ejecutar las pruebas, no fallará las pruebas innecesariamente como lo hace la clase de registro de Android. Timber le brinda un control conveniente sobre la depuración y la compilación de producción de su aplicación.

Tosin John
fuente
4

Gracias a la respuesta de @Paglian y al comentario de @ Miha_x64, pude hacer que lo mismo funcionara para kotlin.

Agregue el siguiente archivo Log.kt en 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...

Y voilà, tus llamadas a Log.xxx deberían llamar a estas funciones en su lugar.

Abel
fuente
2

Mockito no se burla de los métodos estáticos. Utilice PowerMockito en la parte superior. He aquí un ejemplo.

Antiohia
fuente
1
@ user3762991 También necesitas cambiar tus comparadores. No puede usar un comparador en la thenReturn(...)declaración. Necesita especificar un valor tangible. Ver más información aquí
troig
Si el método e, d, v no se puede burlar, solo por esta limitación, ¿mockito se vuelve inutilizable?
user3762991
2
Si no puedes comer un tenedor, ¿se vuelve inutilizable? Simplemente tiene otro propósito.
Antiohia
1

Otra solución es utilizar Robolectric. Si quieres probarlo, comprueba su configuración .

En build.gradle de su módulo, agregue lo siguiente

testImplementation "org.robolectric:robolectric:3.8"

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

Y en tu clase de prueba,

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

En las versiones más recientes de Robolectric (probadas con 4.3) su clase de prueba debería tener el siguiente aspecto:

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

    // tests ...
}
Lipi
fuente
0

Si está usando org.slf4j.Logger, entonces simplemente burlarse del Logger en la clase de prueba usando PowerMockito funcionó para mí.

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}
user0904
fuente
0

Ampliando la respuesta de kosiara para usar PowerMock y Mockito en Java con JDK11 para simular el android.Log.vmétodo con System.out.printlnpruebas unitarias en Android Studio 4.0.1.

Esta es una solución completa en 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!");
    }

}

Recuerde agregar dependencias en el archivo build.gradle de su módulo donde existe su prueba unitaria:

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
fuente