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:
- Peut-on se Moquer de la statique
LoggerFactory.getLogger()
pour toute la classe? - 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.
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.
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 -