java 使用PowerMock和Mockito模拟Logger和LoggerFactory

3duebb1j  于 2023-08-02  发布在  Java
关注(0)|答案(9)|浏览(360)

我有下面的Logger,我想模拟出来,但要验证日志条目被调用,而不是内容。

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()来为任何类工作吗?
1.我似乎只能在@Before中运行when(loggerMock.isDebugEnabled()).thenReturn(true);,因此我似乎无法更改每个方法的特征。有办法解决吗?

编辑调查结果:

我想我已经试过了,它不起作用:

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


谢谢你,因为它起作用了。
然而,我尝试了无数的变化:

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


我不能让loggerMock在@Before之外改变它的行为,但这只发生在Coburtura上。与三叶草,覆盖率显示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!";
    }
    ...
}


然后我做了这个测试:

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


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

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


我没有任何互动。我也试过PowerMockito.verifyStatic();在@Before中,但是也具有零交互。
这看起来很奇怪,Cobertura显示if(logger.isDebugEnabled()){没有100%完成,而三叶草显示了,但两者都同意验证失败。

mrfwxfqh

mrfwxfqh1#

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

字符串
这段代码中的两个重要方面是,您需要在应用静态模拟时确定范围,即:在这个尝试块中。您需要从MockedStatic对象调用存根和验证API。
@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
EDIT 2:由于这似乎是一个流行的问题,我想指出,如果这些日志消息是那么重要,需要测试,即.它们是系统的功能/业务部分**,那么引入一个真实的的依赖关系,使这些日志成为功能,在整个系统设计中会更好**,而不是依赖于标准的静态代码和日志记录器的技术类。
对于这个问题,我建议使用reportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX之类的方法来创建一个类似于= Reporter的类。这样做的好处是,任何阅读代码的人都可以看到该特性。但它也将有助于实现测试,更改此特定功能的实现细节.
因此,您不需要像PowerMock这样的静态模拟工具。在我看来,静态代码可以很好,但一旦测试需要验证或模拟静态行为,就有必要重构并引入清晰的依赖关系。

gjmwrych

gjmwrych2#

派对有点晚了-我正在做类似的事情,需要一些指点,最后来到了这里。没有功劳--我从Brice那里拿走了所有的代码,但得到的“零交互”比Cengiz得到的要少。
使用jheriks和Joseph Lust的指导,我想我知道为什么-我把我的对象作为一个字段进行测试,并在@Before中更新它,不像Brice。那么实际的记录器就不是mock,而是一个真实的的类,就像jhriks建议的那样初始化了……
我通常会为我的被测对象这样做,以便为每个测试获得一个新的对象。当我把字段移到本地并在测试中更新时,它运行正常。然而,如果我尝试第二个测试,它不是我的测试中的模拟,而是第一个测试中的模拟,我再次得到零交互。
当我把mock的创建放在@BeforeClass中时,被测对象中的logger总是mock,但请参阅下面的注解以了解此问题...

测试类

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中进行验证,因为静态调用是堆叠的-verify(mockLOG, times(2)).info("true");-而不是在每个测试中进行times(1),因为第二个测试会失败,说这里有2个调用。这是漂亮的裤子,但我找不到一种方法来清除调用。我想知道有没有人能想出一个解决这个问题的办法...

k0pti3hp

k0pti3hp3#

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

when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock);

字符串

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


关于你的第二个问题(可能还有第一个令人困惑的行为),我认为问题在于logger是静态的。所以

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


class 初始化时执行,而不是在 object 示例化时执行。有时这可能是在同一时间,所以你会没事,但很难保证。因此,您设置LoggerFactory.getLogger来返回您的模拟,但是在设置模拟时,logger变量可能已经用一个真实的的Logger对象设置了。
您可以使用ReflectionTestUtils之类的工具显式地设置日志记录器(我不知道这是否适用于静态字段),或者将其从静态字段更改为示例字段。无论哪种方式,都不需要模拟LoggerFactory.getLogger,因为您将直接注入模拟Logger示例。

64jmpszr

64jmpszr4#

我认为您可以使用Mockito.reset(mockLog)重置调用。您应该在每次测试之前调用它,所以在@Before内部是一个好地方。

gr8qqesn

gr8qqesn5#

class SomeService{
  private static final Logger logger = LogManager.getLogger(SomeService.class);

  public void callSomeMethod(){
   ...
   ...
   if (logger.isDebugEnabled()) {
        logger.debug(String.format("some message ....."));
    }
  }

}

字符串

JUNIT测试覆盖率-

import java.io.IOException;
 
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
 
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
 

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
  @Mock
  private Appender mockAppender;
 
  private Logger logger;
 
  @Before
  public void setup() {
    when(mockAppender.getName()).thenReturn("MockAppender");
    when(mockAppender.isStarted()).thenReturn(true);
    when(mockAppender.isStopped()).thenReturn(false);
 
    logger = (Logger)LogManager.getLogger(SomeService.class);
    logger.addAppender(mockAppender);
    logger.setLevel(Level.DEBUG);
  }
 
  
 
  @Test
  public void callSomeMethodTest() {
    //write test cases, logger.isDebugEnabled() will call.
  }
 
}

ss2ws0br

ss2ws0br6#

使用显式注入。没有其他方法允许您在同一JVM中并行运行测试。
使用类加载器的模式,如静态日志绑定器,或与环境思想相混淆的模式,如logback.XML,在测试时都是失败的。
考虑一下我提到的并行化测试,或者考虑一下您想要拦截组件A的日志记录的情况,组件A的构造隐藏在API之后如果您使用的是从顶部注入的依赖项loggerfactory,则后一种情况很容易处理,但如果您注入Logger,则不容易处理,因为在ILoggerFactory. getLogger的该程序集中没有接缝。
也不全是单元测试。有时我们希望集成测试发出日志记录。有时候我们不会。我们希望一些集成测试日志记录被选择性地抑制,例如预期的错误,否则会使CI控制台混乱和混乱。如果您从您的主线(或您可能使用的任何di框架)的顶部注入ILoggerFactory,那么一切都很容易
所以...
按照建议注入报告程序或采用注入ILoggerFactory的模式。通过显式的ILoggerFactory注入而不是Logger,您可以支持许多访问/拦截模式和并行化。

vddsk6oq

vddsk6oq7#

下面是一个测试类,它模拟类LogUtil中名为log的私有静态final Logger。
除了模拟getLogger工厂调用之外,还需要在@BeforeClass中使用反射显式地设置字段

public class LogUtilTest {

    private static Logger logger;

    private static MockedStatic<LoggerFactory> loggerFactoryMockedStatic;

    /**
     * Since {@link LogUtil#log} being a static final variable it is only initialized once at the class load time
     * So assertions are also performed against the same mock {@link LogUtilTest#logger}
     */
    @BeforeClass
    public static void beforeClass() {
        logger = mock(Logger.class);
        loggerFactoryMockedStatic = mockStatic(LoggerFactory.class);
        loggerFactoryMockedStatic.when(() -> LoggerFactory.getLogger(anyString())).thenReturn(logger);
        Whitebox.setInternalState(LogUtil.class, "log", logger);
    }

    @AfterClass
    public static void after() {
        loggerFactoryMockedStatic.close();
    }
}

字符串

qf9go6mv

qf9go6mv8#

我已经让它在没有PowerMock的情况下工作,而Mockito只用于一个测试。当我用verify进行两个测试时,当我运行类中的所有测试时,第二个用no interactions with this mock开始失败。两个测试分别起作用。
经过一些实验后,只有当我制作模拟日志记录器static时,它才对我有效。

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    private static MockedStatic<LoggerFactory> loggerFactoryMockedStatic;
    private static final Logger logger = mock(Logger.class);
    
    @BeforeClass
    public static void beforeAll() {
        loggerFactoryMockedStatic = mockStatic(LoggerFactory.class);
        loggerFactoryMockedStatic.when(() -> LoggerFactory.getLogger(MyServiceUnderTest.class))
                .thenReturn(logger);
    }

    @AfterClass
    public static void afterAll() {
        loggerFactoryMockedStatic.close();
    }

    @Before
    public void init() {
        Mockito.reset(logger); // important as it's static
        // ...

    @Test
    public void myTest() throws Exception {
        // ...
        verifyNoInteractions(logger);
    }
}

字符串

yiytaume

yiytaume9#

使用白盒的简单解决方案:

@Test
void getMessage() {
    Whitebox.setInternalState(AeAuthTokenOidcMapper.class, "logger", logger);
    when(logger.isDebugEnabled()).thenReturn(true);

    String mes = sut.getMessage();

    assertEquals("hi", mes);
    verify(logger).debug("message");
}

字符串


的数据

相关问题