Moqueuse Logger et LoggerFactory avec PowerMock et Mockito

j'ai le Logger suivant que je veux supprimer, mais pour valider les entrées de log sont appelés, pas pour le contenu.

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

je veux me moquer de toute classe qui est utilisée pour LoggerFactory.getLogger() mais je n'ai pas pu trouver comment faire cela. C'est ce que j'ai pour l'instant:

@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));

    ...
}

j'aimerais savoir:

  1. Peut-on se Moquer de la statique LoggerFactory.getLogger() pour toute la classe?
  2. je ne peux que semblent run when(loggerMock.isDebugEnabled()).thenReturn(true); dans le @Before et donc je ne semble pas pouvoir changer les caractéristiques par méthode. Est-il un moyen de contourner cela?

Modifier les résultats:

je pensais que j'ai essayé déjà et ça n'a pas fonctionné:

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

mais merci, comme ça a marché.

cependant j'ai essayé d'innombrables variantes pour:

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

je n'arrive pas à loggerMock à modifier son comportement à l'extérieur de @Before mais cela arrive seulement avec Coburtura. Avec Clover, la couverture montre 100%, mais il ya encore un problème dans les deux sens.

j'ai cette classe 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!";
    }
    ...
}

Puis-je avoir ce test:

@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
    }
}

dans clover je montre une couverture de 100% de la if(logger.isDebugEnabled()){ bloc. Mais si j'essaie de vérifier l' loggerMock:

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

Je n'obtiens aucune interaction. J'ai aussi essayé PowerMockito.verifyStatic();@Before mais il y a aussi zéro interaction.

cela semble étrange que Cobertura montre if(logger.isDebugEnabled()){ comme n'étant pas complète à 100%, et Clover si, mais les deux conviennent que la vérification échoue.

41
demandé sur Adrian Jałoszewski 2012-01-21 02:57:47

5 réponses

@Mick, essayer de préparer le propriétaire du champ statique aussi, par exemple :

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

EDIT1: je viens de créer un petit exemple. D'abord le contrôleur :

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

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

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

puis le test:

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());
    }
}

notez les importations ! Libs notables dans le chemin de classe: Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


EDIT2 : Comme il semble être une question populaire, je tiens à souligner que si ces messages log sont que important et nécessitent d'être testé, c'est à dire qu'ils sont fonction / entreprise partie du système alors introduire une dépendance réelle qui rend les logs clairs serait tellement mieux dans la conception du système entier, au lieu de s'appuyer sur le code statique d'une classe standard et technique d'un logger.

pour cette question je recommanderais d'élaborer quelque chose comme= a Reporter classe avec des méthodes telles que reportIncorrectUseOfYAndZForActionX ou reportProgressStartedForActionX. Cela aurait l'avantage de rendre la fonctionnalité visible pour quiconque lit le code. Mais cela aidera aussi à réaliser des tests, changer les détails d'implémentations de cette fonctionnalité particulière.

par conséquent, vous n'auriez pas besoin d'outils de moquerie statique comme PowerMock. à mon avis, le code statique peut être correct, mais dès que l'essai exige de vérifier ou de simuler un comportement statique, il est nécessaire de modifier et d'introduire des dépendances claires.

53
répondu Brice 2013-07-12 13:26:08

un peu en retard à la fête - je faisais quelque chose de similaire et j'avais besoin de quelques conseils et j'ai fini ici. Ne prenant aucun crédit - j'ai pris tout le code de Brice mais j'ai eu les "interactions zéro" que Cengiz a eu.

à l'Aide de ce jheriks amd Joseph Luxure avait mis je pense que je sais pourquoi, j'ai eu mon objet sous test comme un champ et newed dans un @Devant à la différence de Brice. Alors le vrai logger n'était pas le mock mais un vrai class init'd comme jhriks suggéré...

je ferait normalement cela pour mon objet sous test afin d'obtenir un nouvel objet pour chaque test. Quand j'ai déménagé le terrain à un local et l'ai newed dans le test il a fonctionné ok. Cependant, si j'ai essayé un second test, ce n'était pas le mock dans mon test mais le mock du premier test et j'ai eu les interactions zéro à nouveau.

quand je mets la création de la maquette dans le @avant-Glass Le logger dans l'objet sous test est toujours la maquette mais voir la note ci-dessous pour les problèmes avec ce...

classe à l'essai

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

    }
}

Test

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());
    }
}

Remarque:

si vous avez deux tests avec la même attente j'ai dû faire la vérification dans la classe @AfterClass que les invocations sur la statique sont empilés vers le haut -