4

I'm wondering what is the easiest and most concise way to mock org.apache.log4j with powermock (and mockito).

I have tried a few approaches (which I won't illustrate here) but have not yet found a way to achieve what I want. I have created a simple class to test below and I want to call the run method and verify that the log.info message has been called. How do I do this? I'm sure it's quite easy when you know how!

(I'm using @Rule as I want to run under spring test but that should make no difference).

Thanks a millon for the correct code.

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

}
Matt Lachman
  • 4,541
  • 3
  • 30
  • 40
fergal_dd
  • 1,486
  • 2
  • 16
  • 27
  • Have you tried mocking the static `Logger` class? This page shows you how: https://code.google.com/p/powermock/wiki/MockStatic – Chris Mantle Sep 25 '13 at 13:25
  • I have tried that but I didn't manage to get it working. The above is so simple an an example, I am hoping that somebody will post an answer with the few lines of code to get this working, rather than suggestions (as I have tried quite a few things) - but thanks for that Chris all the same. – fergal_dd Sep 25 '13 at 14:01
  • 2
    Hm, I'd second Chris' approach to mocking `Logger` in that its static `getLogger` method will return a mocked `Logger` instance. Injecting the Logger instance into the ClassUnderTest is not an option, right? – Fildor Sep 25 '13 at 14:46

2 Answers2

10

Chris and Fildor are correct to try to mock the Logger.getLogger(). Unfortunately the way Log4J works makes that technically tricky.

Here's the code I came up with (tested) based on your sample above.

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

I chose to go with using Whitebox to outright set the static field on the class under test to my mockLogger instance. After that, verification was pretty straightforward.

Matt Lachman
  • 4,541
  • 3
  • 30
  • 40
  • The above code works perfectly. I tried it out. Thanks. It does answer the question. – fergal_dd Sep 25 '13 at 16:17
  • For some reason under spring testing I couldn't get this working until I changed the line with WhiteBox. see http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection – fergal_dd Sep 25 '13 at 16:47
  • I'm glad that worked for you; could you please "accept" the answer? What change did you have to make to the Whitebox line? I haven't used Spring testing with Powermock so I'm curious about the integration issues that come up. – Matt Lachman Sep 25 '13 at 19:02
  • I've "answered" that - might be useful for others. I accepted your answer. Cheers for code. All working perfectly under spring now. – fergal_dd Sep 26 '13 at 08:40
1

Matt Lachman's answer worked perfectly for me - until I tried it using Spring. In Spring, I got an runtime exception when attempting to change the logger to the mockLogger. To get it working in Spring I had to do the following:

change the line

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

to

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

and the EncapsulationBreaker looks like this:

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

}

To read more about setting private static final members see Change private static final field using Java reflection

Also note, I'm only doing this for testing purposes.

Community
  • 1
  • 1
fergal_dd
  • 1,486
  • 2
  • 16
  • 27