Saltar al contenido

Mocking Logger y LoggerFactory con PowerMock y Mockito

Mantén la atención porque en este artículo encontrarás la contestación que buscas.

Solución:

EDITAR 2020-09-21: Desde 3.4.0, Mockito admite burlas static métodos, API todavía se está incubando y es probable que cambie, en particular en lo que respecta a la verificación y el apéndice. Requiere el mockito-inline artefacto. Y no es necesario preparar la prueba ni utilizar ningún corredor específico. Todo lo que necesitas hacer es:

@Test
public void name() 
    try (MockedStatic integerMock = mockStatic(LoggerFactory.class)) 
        final Logger logger = mock(Logger.class);
        integerMock.when(() -> LoggerFactory.getLogger(any(Class.class))).thenReturn(logger);
        new Controller().log();
        verify(logger).warn(any());
    

Los dos aspectos importantes de este código es que debe establecer el alcance cuando el static se aplica simulacro, es decir, dentro de este bloque de prueba. Y debe llamar a la api de verificación y stubbing desde el MockedStatic objeto.


@Mick, intenta preparar al dueño del static campo también, por ejemplo:

@PrepareForTest(GoodbyeController.class, LoggerFactory.class)

EDIT1: Acabo de crear un pequeño ejemplo. Primero 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());
    

¡Tenga en cuenta las importaciones! Libs dignos de mención en la ruta de clases: Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


EDIT2: como 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 una característica / parte comercial del sistema luego, introducir una dependencia real que deje en claro que estos registros son características que serían mucho mejores en todo el diseño del sistema, en lugar de depender de static código de un estándar y clases técnicas de un registrador.

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

Por lo tanto, no necesitarías static burlándose de herramientas como PowerMock. En mi opinión static El código puede estar bien, pero tan pronto como la prueba exija verificar o simular static comportamiento es necesario refactorizar e introducir dependencias claras.

Un poco tarde para 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 obtuve las “interacciones cero” que obtuvo Cengiz.

Usando la guía de lo que jheriks y Joseph Lust habían puesto, creo que sé por qué: tuve mi objeto bajo prueba como un campo y lo renové en un @Before a diferencia de Brice. Entonces, el registrador real no era el simulacro sino una clase real iniciada como sugirió jhriks …

Normalmente haría esto para mi objeto bajo prueba para obtener un objeto nuevo para cada prueba. Cuando moví el campo a un local y lo renové en la prueba, funcionó bien. Sin embargo, si probé una segunda prueba, no fue el simulacro en mi prueba, sino el simulacro de la primera prueba y obtuve las interacciones cero nuevamente.

Cuando pongo la creación del simulacro en @BeforeClass, el registrador en el objeto bajo prueba es siempre el simulacro, pero vea la nota a continuación para ver los problemas con esto …

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, tuve que hacer la verificación en @AfterClass como las invocaciones en el static están apilados – verify(mockLOG, times(2)).info("true"); – en lugar de veces (1) en cada prueba, ya que la segunda prueba fallaría diciendo que hay 2 invocaciones de esto. Esto es bonito, pero no pude encontrar una manera de borrar las invocaciones. Me gustaría saber si alguien puede pensar en una forma de evitar esto …

En respuesta a su primera pregunta, debería ser tan simple como reemplazar:

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

con

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

Con respecto a su segunda pregunta (y posiblemente el comportamiento desconcertante de la primera), creo que el problema es que el registrador static. Entonces,

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

se ejecuta cuando el clase se inicializa, no cuando el objeto se instancia. A veces, esto puede ser aproximadamente al mismo tiempo, por lo que estará bien, pero es difícil garantizarlo. Por lo tanto, configura LoggerFactory.getLogger para devolver su simulacro, pero es posible que la variable del registrador ya se haya configurado con un objeto Logger real en el momento en que se configuran sus simulacros.

Es posible que pueda configurar el registrador explícitamente usando algo como ReflectionTestUtils (no sé si eso funciona con static campos) o cámbielo de un static campo a un campo de instancia. De cualquier manera, no necesita simular LoggerFactory.getLogger porque inyectará directamente la instancia simulada de Logger.

Si guardas algún incógnita y forma de innovar nuestro reseña te evocamos ejecutar una referencia y con gusto lo interpretaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *