Recently, we’ve started using Mockito and PowerMock in our testing. I won’t explain mocking and why or why not you should use it, but I want to share my experience with using PowerMock.
PowerMock
comes with a very strong promise: “PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.”
That is seriously cool, right? I thought so, too, but I stumbled upon several problems the very first time I tried to use it. Frankly, those problems, as always, stemmed from my lack of experience with the tool, but hey – everyone’s a novice at first. Let me share my experience with you.
The Problem
public class ClassUnderTest { public InputStream method(boolean param, URI uri) throws Exception { String scheme = param ? "https" : "http"; URI replacedUri = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment()); return replacedUri.toURL().openStream(); } }
The above fabricated example expresses the essence of the testing challenge I faced (the real class was this.) The method I wanted to test obtains an URI and transforms it based on some parameters. Then it tries to open a stream on the URI so that the caller can download the contents.
Because the URI that the method tries to download from is by design either http or https URL, it is kind of hard to test without actually standing up a HTTP server to serve the file during the test. This is of course not impossible and possibly would not be that hard, but I thought PowerMock
can come here to the rescue. I should be able to mock those calls out in my tests.
Attempt #1 – mocking system classes
@Test @PrepareForTest(ClassUnderTest.class) public class MyTest { @ObjectFactory public IObjectFactory getObjectFactory() { return new PowerMockObjectFactory(); } public void testMethod() throws Exception { URI uriMock = PowerMockito.mock(URI.class); URL urlMock = PowerMockito.mock(URL.class); PowerMockito.whenNew(URI.class).withArguments("http", "localhost", null, null, null).thenReturn(uriMock); Mockito.when(uriMock.toURL()).thenReturn(urlMock); Mockito.when(urlMock.openStream()).thenReturn(new FileInputStream(new File(".", "existing.file"))); ClassUnderTest testObject = new ClassUnderTest(); testObject.method(false, new URI("blah://localhost")); } }
This should be fairly easy to understand for everyone that used some mocking framework. I’m creating two mocks: one for URI and one for URL classes. Then I’m using PowerMock to capture the construction of a new URI (see the code of the ClassUnderTest) and returning my uriMock
. The uriMock is set up to return the urlMock
when its toURL()
method is called. When the openStream()
method is called on my urlMock
, I’m returning an input stream of a local file.
Nice and easy, right? Except it doesn’t work. I get the following stacktrace as soon as I try to mock the URI class:
org.mockito.exceptions.base.MockitoException: Mockito cannot mock this class: class replica.java.net.URI$$PowerMock0 Mockito can only mock visible & non-final classes.
After a bit of googling, the cause is apparent – PowerMock cannot mock the system classes (unless PowerMock java agent is used). Ok, let’s try another approach, this time trying to avoid using mocks.
Attempt #2 – PowerMockito.whenNew(URL.class)
The idea behind this attempt is that PowerMockito
can capture and override constructor calls. Because URI.toURL()
constructs a new URL instance with a single string argument, so we theoretically should be able to intercept that?
public void testMethod() throws Exception { URL realUrl = new File(".", "existing.file").toURI().toURL(); PowerMockito.whenNew(URL.class).withArguments("http://localhost").thenReturn(realUrl); ClassUnderTest testObject = new ClassUnderTest(); testObject.method(false, new URI("blah://localhost")); }
As you might have guessed, this doesn’t work either. And frankly if it did, I’d have some serious questions about how it could. The constructor of URL
is only called inside the toURL()
of the URI
which is a system class that PowerMock can’t touch. So, the third attempt.
Attempt #3 – PowerMockito.whenNew(URI.class)
What is the difference between this one and the previous attempt? Well, it took me a while to decipher the javadoc for the @PrepareForTest
annotation, but it boils down to this. If you need to use the PowerMockito.whenNew
method, you need to tell PowerMock to do bytecode manipulation on the class that (in some method) directly calls given constructor. This is kinda understandable when you know what PowerMock is doing – it will actually change the byte code of the “prepared” class so that any constructor calls (and other things) are checking for the rules defined using whenNew
and other methods. You realize this for real when you try to debug the class under test (that has been prepared by power mock) – you can no longer be sure that what you see in the code is actually what is happening, because the bytecode of the class no longer exactly corresponds to what you see in the source code.
So to sum it up, here’s the code that works:
@Test @PrepareForTest(ClassUnderTest.class) public class MyTest { @ObjectFactory public IObjectFactory getObjectFactory() { return new PowerMockObjectFactory(); } public void testMethod() throws Exception { URI realUri = new File(".", "existing.file").toURI(); PowerMockito.whenNew(URI.class).withArguments("http", "localhost", null, null, null).thenReturn(realUri); ClassUnderTest testObject = new ClassUnderTest(); testObject.method(false, new URI("blah://localhost")); } }
The constructor of the URI
is intercepted and we return a “realUri”, i.e. a different instance of otherwise “normal” URI class. This works, because exactly that constructor with those arguments is called in the class under test that has been manipulated by PowerMock (as instructed by the @PrepareForTest
annotation). From that point on, we don’t need any special behavior on either the URI
or URL
classes and so the code can stay untouched.
Conclusion
The conclusion is basically the famous 4 letters – RTFM 🙂 I just wanted to detail my journey through the dark corners of the PowerMock forest just in case some of you were as confused as I was when I first entered it.
2013/07/02 at 04:02
Actually I am not sure what you wrote is unit test, here is the test I wrote,
package test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import static org.easymock.EasyMock.expect;
import static org.powermock.api.easymock.PowerMock.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassUnderTest.class, InputStream.class, URI.class, URL.class})
public class ClassUnderTestTest {
@Test
public void testMethod() throws Exception {
URI uri = createMock(URI.class);
expect(uri.getAuthority()).andReturn(“X”);
expect(uri.getPath()).andReturn(“XX”);
expect(uri.getQuery()).andReturn(“XXX”);
expect(uri.getFragment()).andReturn(“XXXX”);
expectNew(URI.class,”https”, “X”,”XX”, “XXX”, “XXXX”).andReturn(uri);
URL url = createMock(URL.class);
expect(uri.toURL()).andReturn(url);
expect(url.openStream()).andReturn(createMock(InputStream.class));
replayAll();
new ClassUnderTest().method(true, uri);
verifyAll();
}
}
If you add PwerMock.verifyAll() to your test, will it still pass?
2013/02/21 at 20:09
Thanks for this. You helped me find my issue, which turned out to be wrong class in @PrepareForTest..