4

我想知道用 powermock(和 mockito)模拟 org.apache.log4j 的最简单和最简洁的方法是什么。

我尝试了一些方法(我不会在这里说明),但还没有找到实现我想要的方法。我创建了一个简单的类来测试下面,我想调用 run 方法并验证 log.info 消息是否已被调用。我该怎么做呢?我敢肯定,当您知道如何操作时,这很容易!

(我正在使用@Rule,因为我想在弹簧测试下运行,但这应该没有区别)。

感谢您提供正确的代码。

import org.apache.log4j.Logger;
import org.junit.Rule;
import org.junit.Test;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;

public class MockLog4JTest
{
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    private static class ClassUnderTest
    {
        private static Logger logger = Logger.getLogger(ClassUnderTest.class);

        public void run() {
            logger.info("Hello.");
        }

    }

    @Test
    public void testLog()
    {
        ClassUnderTest classUnderTest = new ClassUnderTest();
        classUnderTest.run();
    }

}
4

2 回答 2

10

Chris 和 Fildor 尝试模拟 Logger.getLogger() 是正确的。不幸的是,Log4J 的工作方式使得这在技术上很棘手。

这是我根据您上面的示例提出(测试)的代码。

ClassUnderTest.java

import org.apache.log4j.Logger;

public class ClassUnderTest {
    private static Logger logger = Logger.getLogger(ClassUnderTest.class);

    public void run() {
        logger.info("Hello.");
    }
}

MockLog4JTest.java

import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.mock;

import org.apache.log4j.Logger;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.powermock.reflect.Whitebox;

@PrepareForTest(ClassUnderTest.class)
public class MockLog4JTest {
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @BeforeClass
    public static void oneTimeSetup() {
        System.setProperty("log4j.defaultInitOverride", Boolean.toString(true));
        System.setProperty("log4j.ignoreTCL", Boolean.toString(true));
    }

    @Test
    public void testLog()
    {
        ClassUnderTest classUnderTest = new ClassUnderTest();
        Logger mockLogger = mock(Logger.class);

        Whitebox.setInternalState(ClassUnderTest.class, "logger", mockLogger);

        classUnderTest.run();

        verify(mockLogger).info(eq("Hello."));
    }
}

我选择使用 Whitebox 将被测类上的静态字段直接设置为我的mockLogger实例。之后,验证非常简单。

于 2013-09-25T15:27:11.343 回答
1

Matt Lachman 的回答对我来说非常有效——直到我使用 Spring 进行了尝试。在 Spring 中,尝试将记录器更改为 mockLogger 时出现运行时异常。为了让它在 Spring 中工作,我必须执行以下操作:

换行

Whitebox.setInternalState(ClassUnderTest.class, "logger", mockLogger);

EncapsulationBreaker.setFinalStatic(ClassUnderTest.class.getDeclaredField("logger"),  mockLogger);

EncapsulationBreaker 看起来像这样:

  public class EncapsulationBreaker
 {
    public static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
 }

}

要阅读有关设置私有静态最终成员的更多信息,请参阅 使用 Java 反射更改私有静态最终字段

另请注意,我这样做只是为了测试目的。

于 2013-09-26T08:36:37.987 回答