2012-01-20 24 views
34

Tengo el siguiente registrador que quiero simular, pero para validar las entradas de registro se están llamando, no para el contenido.Mocking Logger y LoggerFactory con PowerMock y Mockito

private static Logger logger = 
     LoggerFactory.getLogger(GoodbyeController.class); 

quiero para burlarse de cualquier clase que se utiliza para LoggerFactory.getLogger(), pero no pude encontrar la manera de hacer eso. Esto es lo que terminó con hasta ahora:

@Before 
public void performBeforeEachTest() { 
    PowerMockito.mockStatic(LoggerFactory.class); 
    when(LoggerFactory.getLogger(GoodbyeController.class)). 
     thenReturn(loggerMock); 

    when(loggerMock.isDebugEnabled()).thenReturn(true); 
    doNothing().when(loggerMock).error(any(String.class)); 

    ... 
} 

me gustaría saber:

  1. ¿Puedo burlarse de la estática LoggerFactory.getLogger() a trabajar para cualquier clase?
  2. Parece que solo puedo ejecutar when(loggerMock.isDebugEnabled()).thenReturn(true); en el @Before y, por lo tanto, parece que no puedo cambiar las características por método. ¿Hay alguna forma de evitar esto?
  3. hallazgos

Editar:

pensé que ya intentado esto y no funcionó:

when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 

Pero gracias, como lo hizo el trabajo.

Sin embargo, he intentado innumerables variaciones a:

when(loggerMock.isDebugEnabled()).thenReturn(true); 

no puede obtener la loggerMock a cambiar su comportamiento fuera del @Before pero esto sólo ocurre con Coburtura. Con Clover, la cobertura muestra el 100% pero todavía hay un problema en ambos sentidos.

que tienen esta clase simple:

public ExampleService{ 
    private static final Logger logger = 
      LoggerFactory.getLogger(ExampleService.class); 

    public String getMessage() {   
    if(logger.isDebugEnabled()){ 
     logger.debug("isDebugEnabled"); 
     logger.debug("isDebugEnabled"); 
    } 
    return "Hello world!"; 
    } 
    ... 
} 

entonces tengo esta prueba:

@RunWith(PowerMockRunner.class) 
@PrepareForTest({LoggerFactory.class}) 
public class ExampleServiceTests { 

    @Mock 
    private Logger loggerMock; 
    private ExampleServiceservice = new ExampleService(); 

    @Before 
    public void performBeforeEachTest() { 
     PowerMockito.mockStatic(LoggerFactory.class); 
     when(LoggerFactory.getLogger(any(Class.class))). 
      thenReturn(loggerMock); 

     //PowerMockito.verifyStatic(); // fails 
    } 

    @Test 
    public void testIsDebugEnabled_True() throws Exception { 
     when(loggerMock.isDebugEnabled()).thenReturn(true); 
     doNothing().when(loggerMock).debug(any(String.class)); 

     assertThat(service.getMessage(), is("Hello null: 0")); 
     //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails 
    } 

    @Test 
    public void testIsDebugEnabled_False() throws Exception { 
     when(loggerMock.isDebugEnabled()).thenReturn(false); 
     doNothing().when(loggerMock).debug(any(String.class)); 

     assertThat(service.getMessage(), is("Hello null: 0")); 
     //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails 
    } 
} 

en el trébol que muestran una cobertura del 100% del bloque if(logger.isDebugEnabled()){. Pero si intento para verificar la loggerMock:

verify(loggerMock, atLeast(1)).isDebugEnabled(); 

me sale cero interacciones. También intenté PowerMockito.verifyStatic(); en @Before pero eso también tiene cero interacciones.

Esto parece extraño que Cobertura muestre que el if(logger.isDebugEnabled()){ no está completo al 100%, y Clover sí, pero ambos aceptan que la verificación falla.

+0

¿Has probado @MockPolicy? [Los ejemplos aquí] (https://code.google.com/p/powermock/wiki/MockPolicies) son para simular estilos de EasyMock pero se pueden adaptar para Mockito. –

Respuesta

40

@Mick, tratar de preparar el propietario del campo estático también, por ejemplo:

@PrepareForTest({GoodbyeController.class, LoggerFactory.class}) 

EDIT1: acabo creado un pequeño ejemplo. En primer lugar el controlador:

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class Controller { 
    Logger logger = LoggerFactory.getLogger(Controller.class); 

    public void log() { logger.warn("yup"); } 
} 

Entonces la prueba:

import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

import static org.mockito.Matchers.any; 
import static org.mockito.Matchers.anyString; 
import static org.mockito.Mockito.verify; 
import static org.powermock.api.mockito.PowerMockito.mock; 
import static org.powermock.api.mockito.PowerMockito.mockStatic; 
import static org.powermock.api.mockito.PowerMockito.when; 

@RunWith(PowerMockRunner.class) 
@PrepareForTest({Controller.class, LoggerFactory.class}) 
public class ControllerTest { 

    @Test 
    public void name() throws Exception { 
     mockStatic(LoggerFactory.class); 
     Logger logger = mock(Logger.class); 
     when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger); 

     new Controller().log(); 

     verify(logger).warn(anyString()); 
    } 
} 

Nota las importaciones! libs notables en la ruta de clase: Mockito, PowerMock, JUnit, logback núcleos, logback-clásico, SLF4J


Edit2: Por lo que parece ser una pregunta popular, me gustaría señalar que si estos mensajes de registro son tan importantes y requieren ser probados, es decir, son parte de la función/negocio del sistema y luego presentan una dependencia real que deja en claro que estas características son mucho mejores en todo el diseño del sistema, en su lugar de confiar en el código estático de un estándar y clases técnicas de un registrador.

Para este caso, recomendaría crear algo así como = una clase Reporter con métodos como reportIncorrectUseOfYAndZForActionX o reportProgressStartedForActionX. Esto tendría el beneficio de hacer que la característica sea visible para cualquiera que lea el código. Pero también ayudará a lograr pruebas, cambiar los detalles de las implementaciones de esta característica en particular.

Por lo tanto, no necesitaría herramientas de simulación estática como PowerMock. En mi opinión, el código estático puede estar bien, pero tan pronto como la prueba exija verificar o simular el comportamiento estático, es necesario refactorizar e introducir dependencias claras.

+3

NOTA: Si probó una segunda @ Prueba allí, tendrá un problema. verify() no funcionará cuando se vuelva a llamar en una prueba adicional. No si usa @ Before o nuevos nombres var. El classLoader solo va a hacer uno de ellos, por lo que no puede tener dos clases estáticas diferentes. Divide tus pruebas en clases individuales. –

+4

Lamentablemente no puedo hacer que funcione ... Mockito dice ... En realidad, no hubo interacciones con este simulacro. – Cengiz

+0

Funciona bien para mí (incluida la verificación). Asegúrese de estar utilizando @PrepareForTest y también verifique que se haya burlado del método correcto de búsqueda de registro de la fábrica de registros. –

5

En respuesta a su primera pregunta, debe ser tan simple como la sustitución:

when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock); 

con

when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 

En cuanto a su segunda pregunta (y posiblemente el comportamiento desconcertante con la primera), I Creo que el problema es que el registrador es estático.Así,

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class); 

se ejecuta cuando la clasese inicializa, no cuando el objetose instancia. A veces, esto puede ser aproximadamente al mismo tiempo, por lo que estará bien, pero es difícil garantizar eso. Así que configura LoggerFactory.getLogger para devolver su simulación, pero la variable del registrador ya puede haberse configurado con un objeto Logger real en el momento en que se configuran los simuladores.

Puede configurar el registrador de forma explícita usando algo como ReflectionTestUtils (no sé si eso funciona con campos estáticos) o cambiarlo de un campo estático a un campo de instancia. De cualquier manera, no es necesario que se burle de LoggerFactory.getLogger porque estará inyectando directamente la instancia falsa de Logger.

7

Llegó algo tarde a la fiesta. Estaba haciendo algo similar y necesitaba algunos consejos y terminé aquí. Sin tomar crédito, tomé todo el código de Brice pero recibí las "cero interacciones" que recibió Cengiz.

Utilizando la orientación de lo que jheriks y Joseph Lust habían expresado, creo que sé por qué. Tuve mi objeto bajo prueba como campo y lo reinicié en un @Antes de diferencia de Brice. Entonces el registrador real no era el simulacro sino una clase real iniciada como jhriks sugirió ...

Normalmente haría esto para mi objeto bajo prueba a fin de obtener un objeto nuevo para cada prueba. Cuando moví el campo a un local y lo reinicié en la prueba, se ejecutó correctamente. Sin embargo, si probé una segunda prueba, no fue el simulacro en mi prueba, sino el simulacro de la primera prueba y volví a tener las interacciones cero.

Cuando pongo la creación de la maqueta en el @BeforeClass el registrador en el objeto bajo prueba es siempre el simulacro, pero ver la nota abajo de los problemas con este ...

clase bajo prueba

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class MyClassWithSomeLogging { 

    private static final Logger LOG = LoggerFactory.getLogger(MyClassWithSomeLogging.class); 

    public void doStuff(boolean b) { 
     if(b) { 
      LOG.info("true"); 
     } else { 
      LOG.info("false"); 
     } 

    } 
} 

prueba

import org.junit.AfterClass; 
import org.junit.BeforeClass; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

import static org.mockito.Mockito.*; 
import static org.powermock.api.mockito.PowerMockito.mock; 
import static org.powermock.api.mockito.PowerMockito.*; 
import static org.powermock.api.mockito.PowerMockito.when; 


@RunWith(PowerMockRunner.class) 
@PrepareForTest({LoggerFactory.class}) 
public class MyClassWithSomeLoggingTest { 

    private static Logger mockLOG; 

    @BeforeClass 
    public static void setup() { 
     mockStatic(LoggerFactory.class); 
     mockLOG = mock(Logger.class); 
     when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLOG); 
    } 

    @Test 
    public void testIt() { 
     MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); 
     myClassWithSomeLogging.doStuff(true); 

     verify(mockLOG, times(1)).info("true"); 
    } 

    @Test 
    public void testIt2() { 
     MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); 
     myClassWithSomeLogging.doStuff(false); 

     verify(mockLOG, times(1)).info("false"); 
    } 

    @AfterClass 
    public static void verifyStatic() { 
     verify(mockLOG, times(1)).info("true"); 
     verify(mockLOG, times(1)).info("false"); 
     verify(mockLOG, times(2)).info(anyString()); 
    } 
} 

Nota

Si tiene dos pruebas con la misma expectativa que tenía que hacer al verificar en el @AfterClass como las invocaciones de la estática se apilan - verify(mockLOG, times(2)).info("true"); - en lugar de los tiempos (1) en cada ensayo como el segundo la prueba fallaría diciendo allí donde 2 invocación de esto. Esto es bastante pantalones, pero no pude encontrar una manera de borrar las invocaciones. Me gustaría saber si alguien puede pensar en una forma de evitar esto ...

+1

La sugerencia de Markus Wendl de utilizar el restablecimiento funcionó para mí. –

2

Creo que puede restablecer las invocaciones utilizando Mockito.reset (mockLog). Deberías llamar a esto antes de cada prueba, por lo que dentro de @Before sería un buen lugar.

0

Usar inyección explícita. Ningún otro enfoque le permitirá, por ejemplo, ejecutar pruebas en paralelo en la misma JVM.

Patrones que utilizan cualquier cargador de clase como el archivador de registro estático o jugando con el medio ambiente piensa como logback.XML son irrelevantes cuando se trata de pruebas.

Considere las pruebas paralelizadas que menciono, o considere el caso en el que desea interceptar el registro del componente A cuya construcción está oculta detrás de api B. Este último caso es fácil de tratar si está utilizando un loggerfactory inyectado de dependencia del arriba, pero no si usted inyecta Logger ya que no hay costura en este ensamblaje en ILoggerFactory.getLogger.

Y no todo se trata de pruebas unitarias tampoco. A veces queremos pruebas de integración para emitir el registro. A veces no lo hacemos. Alguien quiere que parte del registro de prueba de integración sea suprimido selectivamente, por ejemplo, para errores esperados que de otra manera saturarían la consola de CI y confundirían. Nada fácil si se inyecta ILoggerFactory desde la parte superior de la línea principal (o lo que sea marco di podría usar)

Entonces ...

inyectará un reportero como se sugiere o adoptar un patrón de inyección de la ILoggerFactory. Mediante la inyección explícita de ILoggerFactory en lugar de Logger puede admitir muchos patrones de acceso/intercepción y paralelización.

Cuestiones relacionadas