使用PowerMock和Mockito模拟Logger和LoggerFactory

Mic*_*son 56 java junit slf4j mockito powermock

我有以下Logger我想模拟,但验证日志条目被调用,而不是内容.

private static Logger logger = 
        LoggerFactory.getLogger(GoodbyeController.class);
Run Code Online (Sandbox Code Playgroud)

我想模拟用于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));

    ...
}
Run Code Online (Sandbox Code Playgroud)

我想知道:

  1. 我可以模拟静态LoggerFactory.getLogger()以适用于任何类吗?
  2. 我只能似乎运行when(loggerMock.isDebugEnabled()).thenReturn(true);@Before,因此我似乎无法改变每个方法的特点.有没有解决的办法?

编辑发现:

我以为我已经尝试了这个并且它没有用:

 when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);
Run Code Online (Sandbox Code Playgroud)

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

但是我尝试了无数的变化:

when(loggerMock.isDebugEnabled()).thenReturn(true);
Run Code Online (Sandbox Code Playgroud)

我不能让loggerMock改变它的行为,@Before但这只发生在Coburtura上.使用Clover,覆盖率显示为100%,但仍然存在问题.

我有这个简单的课程:

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!";
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后我有这个测试:

@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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

verify(loggerMock, atLeast(1)).isDebugEnabled();
Run Code Online (Sandbox Code Playgroud)

我得到零互动.我也试过PowerMockito.verifyStatic(); 在@Before但也没有相互作用.

这似乎很奇怪,Cobertura表示if(logger.isDebugEnabled()){并非100%完成,而Clover确实如此,但两人都同意验证失败.

Bri*_*ice 65

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

@PrepareForTest({GoodbyeController.class, LoggerFactory.class})
Run Code Online (Sandbox Code Playgroud)

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

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

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

    public void log() { logger.warn("yup"); }
}
Run Code Online (Sandbox Code Playgroud)

然后测试:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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


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

对于这个问题,我建议Reporter使用诸如reportIncorrectUseOfYAndZForActionXor之类的方法来制作类似= 类的东西reportProgressStartedForActionX.这样做的好处是可以让任何人阅读代码都能看到该功能.但它也有助于实现测试,更改此特定功能的实现细节.

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

  • 注意:如果您在那里尝试了第二个@Test,那么您将遇到问题.在另外的测试中再次调用时,verify()将不起作用.如果您使用@ Before或新的var名称,则不会.classLoader只会生成其中一个,因此您不能拥有两个不同的静态类.将您的测试分解为单独的课程. (5认同)
  • 不幸的是,我无法让它工作...... Mockito说..实际上,与这个模拟没有任何交互. (5认同)

小智 15

派对有点晚了 - 我做了类似的事情,需要一些指针,最后来到这里.以无信用 - 我把所有从布里斯的代码,但得到了"零个互动"比Cengiz了.

使用joreiks和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");
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

测试

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

注意

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

  • +500。@BeforeClass 对我来说很重要。 (2认同)

jhe*_*cks 5

在回答你的第一个问题时,它应该像更换一样简单:

   when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock);
Run Code Online (Sandbox Code Playgroud)

   when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);
Run Code Online (Sandbox Code Playgroud)

关于你的第二个问题(可能是第一个问题的令人费解的行为),我认为问题在于记录器是静态的.所以,

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class);
Run Code Online (Sandbox Code Playgroud)

在初始化时执行,而不是在实例化对象时执行.有时这可能是在同一时间,所以你会好的,但很难保证.因此,您设置LoggerFactory.getLogger以返回您的模拟,但是在您的模拟设置时,可能已经使用真实的Logger对象设置了logger变量.

您可以使用ReflectionTestUtils(我不知道它是否适用于静态字段)或将其从静态字段更改为实例字段来显式设置记录器.无论哪种方式,您都不需要模拟LoggerFactory.getLogger,因为您将直接注入模拟Logger实例.