Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking Logger and LoggerFactory with PowerMock and Mockito

I have the following Logger I want to mock out, but to validate log entries are getting called, not for the content.

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

I want to Mock ANY class that is used for LoggerFactory.getLogger() but I could not find out how to do that. This is what I ended up with so far:

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

I would like to know:

  1. Can I Mock the static LoggerFactory.getLogger() to work for any class?
  2. I can only seem to run when(loggerMock.isDebugEnabled()).thenReturn(true); in the @Before and thus I cannot seem to change the characteristics per method. Is there a way around this?

Edit findings:

I thought I tried this already and it didnt work:

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

But thank you, as it did work.

However I have tried countless variations to:

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

I cannot get the loggerMock to change its behavior outside of @Before but this only happens with Coburtura. With Clover, the coverage shows 100% but there is still an issue either way.

I have this simple class:

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!";     }     ... } 

Then I have this 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     } } 

In clover I show 100% coverage of the if(logger.isDebugEnabled()){ block. But if I try to verify the loggerMock:

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

I get zero interactions. I also tried PowerMockito.verifyStatic(); in @Before but that also has zero interactions.

This just seems strange that Cobertura shows the if(logger.isDebugEnabled()){ as being not 100% complete, and Clover does, but both agree the verification fails.

like image 512
Mick Knutson Avatar asked Jan 20 '12 22:01

Mick Knutson


People also ask

Can we use PowerMock and Mockito together?

Of course you can – and probably will – use Mockito and PowerMock in the same JUnit test at some point of time.

Is PowerMock a mocking framework?

PowerMock is an open-source Java framework used for creating a mock object in unit testing. It extends other mocking frameworks such as EasyMock and Mockito to enhance the capabilities.

How do you use mock and Mockito?

Mock will be created by Mockito. Here we've added two mock method calls, add() and subtract(), to the mock object via when(). However during testing, we've called subtract() before calling add(). When we create a mock object using create(), the order of execution of the method does not matter.

What is mock and Mockito?

Mockito is a mocking framework, JAVA-based library that is used for effective unit testing of JAVA applications. Mockito is used to mock interfaces so that a dummy functionality can be added to a mock interface that can be used in unit testing.


1 Answers

EDIT 2020-09-21: Since 3.4.0, Mockito supports mocking static methods, API is still incubating and is likely to change, in particular around stubbing and verification. It requires the mockito-inline artifact. And you don't need to prepare the test or use any specific runner. All you need to do is :

@Test public void name() {     try (MockedStatic<LoggerFactory> 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());     } } 

The two inportant aspect in this code, is that you need to scope when the static mock applies, i.e. within this try block. And you need to call the stubbing and verification api from the MockedStatic object.


@Mick, try to prepare the owner of the static field too, eg :

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

EDIT1 : I just crafted a small example. First the controller :

import org.slf4j.Logger; import org.slf4j.LoggerFactory;  public class Controller {     Logger logger = LoggerFactory.getLogger(Controller.class);      public void log() { logger.warn("yup"); } } 

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

Note the imports ! Noteworthy libs in the classpath : Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


EDIT2 : As it seems to be a popular question, I'd like to point out that if these log messages are that important and require to be tested, i.e. they are feature / business part of the system then introducing a real dependency that make clear theses logs are features would be a so much better in the whole system design, instead of relying on static code of a standard and technical classes of a logger.

For this matter I would recommend to craft something like= a Reporter class with methods such as reportIncorrectUseOfYAndZForActionX or reportProgressStartedForActionX. This would have the benefit of making the feature visible for anyone reading the code. But it will also help to achieve tests, change the implementations details of this particular feature.

Hence you wouldn't need static mocking tools like PowerMock. In my opinion static code can be fine, but as soon as the test demands to verify or to mock static behavior it is necessary to refactor and introduce clear dependencies.

like image 96
Brice Avatar answered Sep 28 '22 17:09

Brice