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();
}
}
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.
import org.apache.log4j.Logger;
public class ClassUnderTest {
private static Logger logger = Logger.getLogger(ClassUnderTest.class);
public void run() {
logger.info("Hello.");
}
}
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'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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With