Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powermock - mocking org.apache.log4j.Logger? How can I?

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

}
like image 835
fergal_dd Avatar asked Dec 20 '22 23:12

fergal_dd


2 Answers

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.

like image 82
Matt Lachman Avatar answered Jan 06 '23 11:01

Matt Lachman


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.

like image 27
fergal_dd Avatar answered Jan 06 '23 12:01

fergal_dd