Archive for February, 2012

PowerMock Puzzler

Thursday, February 2nd, 2012

PowerMock is a very handy extension to the Mockito mocking framework. It extends Mockito by allowing the mocking of static methods and final classes. Obviously to do this some interesting mangling is going on inside the JVM. Occasionally it is possible to run into some very confusing error messages.

Yesterday my colleague [Candle] and I spent a very vexing half hour over this puzzle before we figured out the answer. So I though I would share it to see how long it takes others to figure it out. ^^

Consider the following class. Do not worry that it basically does nothing. This is just the minimum to show the puzzle.

import java.net.URL;
import com.google.common.cache.CacheLoader;

public class Loader extends CacheLoader<URL, String> {
	@Override
	public String load (URL u) throws Exception {
		u.openConnection();
		return null;
	}
}

And then consider this test class.

import java.net.URL;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class })
public class LoaderTest {
	@Test
	public void test () throws Exception {
		URL url = PowerMockito.mock(URL.class);
		Loader loader = new Loader();
		loader.load(url);
	}
}

When run this test will fail with the following exception. I have edited it slightly to match the cuttings above. It throws in Loader.load() at the call to u.openConnection().

java.lang.AbstractMethodError: java/net/URLStreamHandler.openConnection(Ljava/net/URL;)Ljava/net/URLConnection;
at java.net.URL.openConnection(URL.java:957)
at Loader.load(Loader.java:7)

Now try and figure out what is going on here. Go go go! And for bonus points, suggest a solution that will fix it enough to make the test pass.

EDIT on 2012-09-15:
Solution

Somewhat late, here is the solution: Add the class under test to the @PrepareForTest statement in the test:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ URL.class, Loader.class })
public class LoaderTest {

The problem is that the URL class is being loaded in such a way that the Loader class sees a different class definition for URL compared to the LoaderTest class. The Loader class gets the real URL class instead of the modified one provided by PowerMockito. Adding the class under test to the @PrepareForTest ensures that the Loader class is loaded with the modified class loaded provided by PowerMockito.

Simple, eh? ^^