2012-01-20 190 views
34

我有以下Logger我想模拟出来,但是验证日志条目是被调用的,而不是内容。使用PowerMock和Mockito嘲笑Logger和LoggerFactory

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改变@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%完整,而Clover确实如此,但都同意验证失败。

+0

你试过了@MockPolicy吗? [示例](https://code.google.com/p/powermock/wiki/MockPolicies)适用于EasyMock风格的嘲笑,但可以适用于Mockito。 –

回答

40

@Mick,尽量准备与静电场的老板也一样,如:

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

EDIT1:我刚才制作的一个小例子。首先控制器:

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

注意进口! 值得注意库在classpath:的Mockito,PowerMock,JUnit中,的logback核心,的logback-CLASIC,SLF4J


EDIT2:因为它似乎成为一种流行的问题,我想指出的是,如果这些日志消息是重要的,需要测试,即他们是系统的功能/业务部分然后引入一个真正的依赖关系,使清除这些日志功能将是一个更好的整个系统设计,而不是依靠标准和技术类的记录器的静态代码。

对于这个问题,我会建议用类似reportIncorrectUseOfYAndZForActionXreportProgressStartedForActionX等方法来制作类似= a Reporter类的东西。这将有助于使阅读代码的任何人都可以看到该功能。但它也将有助于实现测试,更改此特定功能的实现细节。

因此,你不需要像PowerMock这样的静态模拟工具。 在我看来,静态代码可以很好,但只要测试需要验证或嘲笑静态行为,就需要重构并引入清晰的依赖关系。

+3

注意:如果您在那里尝试第二次@测试,您将遇到问题。在另外的测试中再次调用verify()将不起作用。如果您使用@ Before或新的var名称,则不需要。 classLoader只会生成其中的一个,所以你不能有两个不同的静态类。把你的测试分成单独的课程。 –

+4

不幸的是,我不能得到它的工作... Mockito说..其实,这与模拟零交互。 – Cengiz

+0

适合我(包括验证)。确保您使用的是@PrepareForTest,并确认您已嘲笑日志工厂的正确日志查找方法。 –

5

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

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

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

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

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

被执行时被初始化,而不是当对象被实例化。有时这可能几乎是在同一时间,所以你会没事的,但很难保证。所以你设置了LoggerFactory.getLogger来返回你的模拟,但是当你的mock被设置时,logger变量可能已经被设置了一个真正的Logger对象。

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

7

对派对稍迟 - 我做了类似的事情,需要一些指示,最终在这里。没有信用 - 我拿走了Brice的所有代码,但得到了Cengiz得到的“零交互”。

利用约瑟夫·拉斯特所做的jheriks和约瑟夫·拉斯特的指导,我想我知道为什么 - 我把我的对象作为一个领域进行测试,并且在@Before的新事件中与布赖斯不同。然后,实际的记录器不是模拟,而是一个真正的类初始化,如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验证作为静态的调用都叠起来 - verify(mockLOG, times(2)).info("true"); - 而不是时间(1)在每个测试作为第二测试会失败,说有2调用这个。这很漂亮,但我找不到清除调用的方法。我想知道是否有人可以想到围绕此....

+1

马库斯温德尔的建议使用重置为我工作。 –

2

我想你可以重置使用Mockito.reset(mockLog)的调用。你应该在每次测试之前调用这个,所以@Before里面会是个好地方。

0

使用显式注入。 没有其他方法可以让您在同一个JVM中并行运行测试。

使用类似于静态日志绑定程序或类似环境的任何类加载器的模式认为像logback.XML在测试时就会崩溃。

考虑一下我提到的并行化测试,或者考虑你想截获其构造被隐藏在API后面的组件A的日志记录的情况。后一种情况很容易处理,如果你使用依赖注入loggerfactory顶部,但不是如果你注入记录器,因为在ILoggerFactory.getLogger的这个程序集中没有接缝。

而且它不是全部关于单元测试。有时我们想要集成测试来发布日志记录。有时候我们没有。有人希望某些集成测试日志记录能够被有选择地抑制,例如,预期的错误会导致CI控制台混乱并混淆。所有容易,如果你从主线的顶部注入ILoggerFactory(或任何DI框架您可以使用)

所以......

无论是注入了记者的建议或采用注射ILoggerFactory的模式。通过显式的ILoggerFactory注入而不是Logger,你可以支持许多访问/截取模式和并行化。