PowerMock is well-known in the Java community and it’s one of these libraries people have a love-hate relationship with. It’s technically quite complex with custom classloaders, agents, byte-code manipulation and deep integration with test and mock frameworks. Even though I was a part of founding the library (which is now mainly being maintained by the awesome Arthur Zagretdinov) I have to admit that I rarely find the need to use it myself. It’s not because it doesn’t work, it’s just because you can solve most of the problems without it. As indicated by the PowerMock website one should be careful and consider alternatives carefully before going all in:
Please note that PowerMock is mainly intended for people with expert knowledge in unit testing. Putting it in the hands of junior developers may cause more harm than good.
While this may sound a bit pompous I’ve literally seen horrible code being tested with an excessive use of PowerMock. But this actually proves that PowerMock work the way it’s intended, it allows you to virtually unit test everything in Java in isolation. The hard part is to know when enough is enough and try to solve the problem in a different way, and this requires experience.
But the reason I’m writing this blog is to show a use case where PowerMock could arguably be an ok solution (I limit myself to “ok” intensionally, more on that later).
Use Case
We were recently integrating an application with a third-party API that returned a hash that could be used to validate the integrity of the response. But what should we do if we failed to validate the response integrity? This is actually quite interesting since even if we fail to validate it, is the response really tampered with or could it be that we’ve made a misstake in the validation logic? Since the application is not part of our core domain and we deem it highly unlikely that the integrity will be tampered with we decided to just log an error to the console and continue. But obviously we want to know if it happens so that we can look into it. Since we’re on the Google Cloud stack we can use stackdriver to alert us if it detects the error message:
log.error("Failed to validate the response integrity, ...")
It’s of course important that no one accidentally removes or change this message in the future and thus we’d like to test that this error message is indeed logged as intended.
The Test
And this is where PowerMock can help. But why? Well let’s see how the log
instance is defined:
public class ThirdPartyAPI {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(ThirdPartyAPI.class);
...
}
The log
instance is both private, static and final. This is typically a good indication that you shouldn’t try to tamper with it and find another solution. But PowerMock actually has pretty good support for stubbing out slf4j that we’ll look into in a second. But first let’s look a little deeper into why this is actually a hard problem at all. Let’s rewrite the ThirdPartyAPI
class a bit:
public class ThirdPartyAPI {
private static final org.slf4j.Logger log;
static {
log = LoggerFactory.getLogger(ThirdPartyAPI.class);
}
...
}
This will actually yield the same result as the previous example. What happens is that static fields are initialized in a static constructor which may be implicit (as is the case in example 1). What we’d like to do is to replace the Logger instance with a stub but it’s not possible unless we remove the final
modifier (which make the field a constant). We also don’t want to initialize the Logger at all, since this unnecessary in our case. In PowerMock we can suppress the static initializer and the final
modifier is removed automatically when using the PowerMock JUnit runner. We could then do like this with PowerMock to replace the Logger instance with a Mockito mock (but please don’t):
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.company.ThirdPartyAPI")
public class MyTest {
@Test
public void testing() {
Logger loggerMock = mock(Logger.class); // Create a Mockito Mock
Whitebox.setInternalState(ThirdPartyAPI.class, loggerMock);
...
}
...
}
Here we use Whitebox to set the Logger field to our newly created mock. If you try to run this example you’ll might see something like this being printed to the console:
log4j:ERROR A "org.apache.log4j.RollingFileAppender" object is not assignable to a org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [org.powermock.core.classloader.MockClassLoader@aa9835] whereas object of type
log4j:ERROR "org.apache.log4j.RollingFileAppender" was loaded by [sun.misc.Launcher$AppClassLoader@11b86e7].
log4j:ERROR Could not instantiate appender named "R".
This is because PowerMock is not loading Appender
through its classloader. The way to solve this is to prepare it for test which will make PowerMock load it through its classloader:
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.company.ThirdPartyAPI")
@PrepareForTest(Appender.class)
public class MyTest {
@Test
public void testing() {
Logger loggerMock = mock(Logger.class); // Create a Mockito Mock
Whitebox.setInternalState(ThirdPartyAPI.class, loggerMock);
...
}
...
}
Now we’re ready to start testing… But now is a good time to take a step back, should we actually do all this? Is there a better way? As it turns out there is! PowerMock has the ability to create something called mock policies that allows one to stub out entire frameworks in a re-useable manner. PowerMock ships with a mock policy designed especially for this case called Slf4jMockPolicy
. This mock policy essentially abstracts away everything we just did manually. So we could rewrite the code above to this:
@RunWith(PowerMockRunner.class)
@MockPolicy(Slf4jMockPolicy.class)
public class MyTest {
@Test
public void testing() {
...
}
...
}
This is indeed nicer and more maintainable. But how do we actually test that the error message is indeed being logged as expected? Here’s one way:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.powermock.api.mockito.mockpolicies.Slf4jMockPolicy;
import org.powermock.core.classloader.annotations.MockPolicy;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import com.company.ThirdPartyAPI;
@RunWith(PowerMockRunner.class)
@MockPolicy(Slf4jMockPolicy.class)
public class MyTest {
@Captor
private ArgumentCaptor loggingCaptor;
@Test
public void testing() {
// Given
Input input = ...
ThirdPartyAPIResponse response = ...
// When
ThirdPartyAPI.validateResponseIntegrity(response, input);
// Then
Logger logger = Whitebox.getInternalState(ThirdPartyAPI.class, Logger.class);
verify(logger).error(loggingCaptor.capture());
assertThat(loggingCaptor.getValue()).startsWith("Failed to validate the response integrity");
}
}
To clarify, validateResponseIntegrity
is the method that logs the error statement if the response integrity couldn’t be validated. The Logger mock instance is created and is injected to the ThirdPartAPI
class automatically by the Slf4jMockPolicy
. There’s one caveat though, we still use Whitebox
to get a hold of the injected Logger mock instance. It’s not too bad though, we never reference the field by its name (only by its type) so we can change it without breaking the test. I would argue that this test is “ok”. Yes it uses PowerMock, but it’s still quite readable and somewhat refactor friendly despite the use of Whitebox
. But is it really a good case for PowerMock? Can we do better?
A case for PowerMock?
Again let’s take a step back. Can we do something differently here to reduce the complexity? One way would be to mock the underlying Appender
and configure the logging framework to use it. This is described for example in this blog post. If you do like this you don’t even need PowerMock and that is certainly appealing! The downside is that this would couple the test code to a particular slf4j implementation (in this case log4j) and one could even argue that the intent is better expressed with the PowerMock example above (especially if you were to wrap the Whitebox
call in a new method expressing it’s intent but hiding the implementation).
Conclusion
If think this example demonstrates some of the difficulties of using a PowerMock-like library. One could easily stumble down a dangerous path (as demonstrated by “example 4”, which could be made even worse by the way) but with some reflection, experience and knowledge you can end up in a place that’s ok from both an intent and maintainability point of view (example 5). Then again one has to question whether one really has to use PowerMock at all. In this case there could be other solutions that doesn’t require the need to bring in an additional library. I would say that this alone is a strong case for not bringing in PowerMock. But if you’re already using it in the project you have to decide which approach is preferable and mind the different trade-offs.
5 thoughts on “A case for PowerMock?”
Have you by chance dabbled at all in Spock? I’ve stopped using Mockito, PowerMock, JUnit and Hamcrest in favor of Spock. I suggest checking it out.
Yes I’ve looked into it a bit. I think one big reason is that it’s Groovy and the others are plain Java.
I wouldn’t say it’s a good use case for mocking. Instead, the test could use the fact that any logging library is highly configurable, and that it isn’t difficult to read logged messages from a test.
In this particular case, here is how it can be done, very simply and with no mocking:
@Test
public void testing() {
// Redirects the error stream so it can be inspected.
OutputStream out = new ByteArrayOutputStream();
System.setErr(new PrintStream(out));
ThirdPartyAPI.validateResponseIntegrity(…);
String loggedMsg = out.toString();
assertTrue(loggedMsg.contains(“Failed to validate the response integrity”));
}
SLF4J is configured to use its “slf4j-simple” implementation, which simply writes to standard err by default (in a pom.xml or similar):
org.slf4j slf4j-simple
1.7.24 test
The reality is that mocking is almost always a bad idea. Even though I developed JMockit (with which the test can also be written quite simply by using a `@Capturing Logger`), I only use the mocking API in very rare cases, and even then only as a temporary solution.
Thanks for the tip, this is indeed a better approach. And I agree that mocking is often a bad idea that is misused a lot.
In order to test logging I like to simply use constructor injection.
“`
public class ThirdPartyAPI {
private static final Logger DEFAULT_LOGGER = LoggerFactory.getLogger(ThirdPartyAPI.class);
private final Logger logger;
public ThirdPartyAPI() {
this(DEFAULT_LOGGER);
}
protected ThirdPartyAPI(Logger logger) {
this.logger = logger;
}
..
}
“`
My test would then look like this:
“`
Logger loggerMock = mock(Logger.class);
ThirdPartyAPI sut = new ThirdPartyAPI(loggerMock);
sut.validateResponseIntegrity(response, input);
verify(loggerMock).error(loggingCaptor.capture());
..
“`
This looks a tad cleaner to me.