Mocking Logger and LoggerFactory with PowerMock and Mockito

Mick Knutson picture Mick Knutson · Jan 20, 2012 · Viewed 111.3k times · Source

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.

Answer

Brice picture Brice · Jan 29, 2012

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.