Mocking Logger and LoggerFactory with PowerMock and Mockito

2022-08-31 14:23:49

我有以下记录器,我想模拟出来,但要验证日志条目被调用,而不是内容。

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

我想模拟任何用于LoggerFactory.getLogger()的类,但我无法找到如何做到这一点。这就是我到目前为止得到的:

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

    ...
}

我想知道:

  1. 我可以模拟静态来为任何类工作吗?LoggerFactory.getLogger()
  2. 我似乎只能在 中运行,因此我似乎无法更改每个方法的特征。有没有办法解决这个问题?when(loggerMock.isDebugEnabled()).thenReturn(true);@Before

编辑结果:

我以为我已经尝试过了,但它不起作用:

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

但是谢谢你,因为它确实有效。

但是,我已经尝试了无数的变体:

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

我无法让loggerMock在外面改变它的行为,但这只发生在Coburtura身上。对于三叶草,覆盖率显示为100%,但无论哪种方式仍然存在问题。@Before

我有这个简单的类:

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

然后我有这个测试:

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

在三叶草中,我显示了100%的块覆盖率。但是,如果我尝试验证:if(logger.isDebugEnabled()){loggerMock

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

我没有得到任何互动。我也试过了;但那也有零相互作用。PowerMockito.verifyStatic()@Before

这似乎很奇怪,Cobertura表明它不是100%完整的,而Clover确实如此,但两者都同意验证失败。if(logger.isDebugEnabled()){


答案 1

EDIT 2020-09-21:自3.4.0以来,Mockito支持模拟静态方法,API仍在孵化中,可能会发生变化,特别是在存根和验证方面。它需要工件。而且您无需准备测试或使用任何特定的运行器。您需要做的就是:mockito-inline

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

此代码中的两个重要方面是,您需要在静态模拟应用时确定范围,即在此 try 块内。您需要从对象调用存根和验证 API。MockedStatic


@Mick,尝试也准备静态字段的所有者,例如:

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

编辑1:我刚刚制作了一个小例子。首先是控制器:

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

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

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

然后测试:

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

注意进口!类路径中值得注意的库:Mockito,PowerMock,JUnit,logback-core,logback-clasic,slf4j


EDIT2 :由于这似乎是一个流行的问题,我想指出,如果这些日志消息非常重要并且需要测试,即它们是系统的功能/业务部分,那么引入一个真正的依赖关系,清楚地表明这些日志是功能,在整个系统设计中会更好, 而不是依赖于标准和记录器的技术类的静态代码。

对于这个问题,我建议使用诸如 或 .这样做的好处是使该功能对阅读代码的任何人都可见。但它也将有助于实现测试,更改此特定功能的实现细节。ReporterreportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX

因此,您不需要像PowerMock这样的静态模拟工具。在我看来,静态代码可以很好,但是一旦测试需要验证或模拟静态行为,就有必要重构并引入明确的依赖关系。


答案 2

派对有点晚了 - 我正在做类似的事情,需要一些指点,最后在这里。没有功劳 - 我从Brice那里获得了所有代码,但得到了比Cengiz得到的“零交互”。

使用jheriks和Joseph Lust的指导,我想我知道为什么 - 我把我的对象作为一个领域进行测试,并将其更新为一个@Before不像Brice。然后,实际的记录器不是模拟的,而是像jhriks建议的那样是一个真正的类初始化......

我通常会为被测对象执行此操作,以便为每个测试获得一个新对象。当我将字段移动到本地并在测试中将其更新时,它运行正常。但是,如果我尝试第二次测试,那不是我测试中的模拟,而是第一次测试的模拟,我再次获得了零交互。

当我将模拟的创建放在@BeforeClass被测对象中的记录器始终是模拟,但请参阅下面的注释以了解此问题...

测试中的类

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

    }
}

测试

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

注意

如果你有两个具有相同期望的测试,我必须在@AfterClass中进行验证,因为静态上的调用是堆积起来的 - 而不是每个测试中的times(1),因为第二个测试会失败,在那里有2个调用这个。这是漂亮的裤子,但我找不到清除调用的方法。我想知道是否有人能想到解决这个问题的方法....verify(mockLOG, times(2)).info("true");


推荐