PowerMock for Integration Testing

A while ago I blogged about how to use the PowerMock Rule to bootstrap PowerMock without using a JUnit runner. A problem that you’ll likely run into sooner or later when using this approach in an integration test is the need to ignore certain classes or packages from being loaded by the PowerMock classloader using the @PowerMockIgnore annotation. This is actually not limited to the Rule but happens when using the standard JUnit runner as well but it’s much more likely to occur in an integration test. Frequent candidates for this are various XML, log, and persistence frameworks . The reason is that some frameworks tries to instantiate classes using reflection and does this from the thread context classloader (PowerMock’s classloader) but then tries to assign the created object to a field not loaded by the same classloader. So by using @PowerMockIgnore you can can instruct PowerMock to defer the loading of a certain package to the parent classloader. What you need to ignore is case specific but usually it’s e.g. the XML framework or some packages that interact with it. E.g. @PowerMockIgnore({"org.xml.*", "javax.xml.*"}).

Example

Let’s say we have a (very stupid) Spring bean like this:

@Component
public class MyBean {

    @Autowired
    private CompanyRepository companyRepository;

    public Message generateMessage() throws SAXException {
        final String[] allEmployees = companyRepository.getAllEmployees();
        final String message = StringUtils.join(allEmployees, ", ");

        final long id = IdGenerator.generateNewId();
        return new Message(id, message);
    }
}

and for demonstration we’d like to mock the static method call to IdGenerator.generateNewId(). Let’s write a simple test bootstrapped by Spring using the classloader version of PowerMockRule:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/example-context.xml")
@PrepareForTest(IdGenerator.class)
public class SpringExampleTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Autowired
    private MyBean myBean;

    @Test
    public void mockStaticMethod() throws Exception {
        // Given
        final long expectedId = 2L;
        mockStatic(IdGenerator.class);
        when(IdGenerator.generateNewId()).thenReturn(expectedId);

        // When
        final Message message = myBean.generateMessage();

        // Then
        assertEquals(expectedId, message.getId());
        assertEquals("John Doe, Paul Anderson, Jane Doe", message.getContent());
    }

If you run this test you’ll get the following error:

java.lang.RuntimeException: java.lang.ClassCastException: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl cannot be cast to javax.xml.parsers.DocumentBuilderFactory
	at powermock.examples.spring.CompanyRepository.getAllEmployees(CompanyRepository.java:42)
	at powermock.examples.spring.MyBean.generateMessage(MyBean.java:31)
	at org.powermock.examples.spring.mockito.SpringExampleTest.mockStaticMethod(SpringExampleTest.java:58)
...

The reason is that companyRepository.getAllEmployees() implementation called from MyBean bean above parses the employee list using Java’s XML parsing (javax.xml.parsers.DocumentBuilderFactory). Thus we must use the PowerMockIgnore annotation to ignore the framework:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/example-context.xml")
@PrepareForTest(IdGenerator.class)
@PowerMockIgnore({"org.w3c.*", "javax.xml.*"})
public class SpringExampleTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    ....
    // Same code as in the previous example

Re-run the test and it’ll now pass.

Introducing PowerMock Java Agent

To make it easier to use PowerMock for integration testing version 1.4.9 introduces yet another way to bootstrap the framework, this time using a java agent. Instead of using classloaders to bootstrap and perform byte-code manipulation we now use Java’s instrumentation API. This means that you won’t run into classloading issues as those presented in the previous example. The test looks exactly the same though:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/example-context.xml")
@PrepareForTest(IdGenerator.class)
public class SpringExampleTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Autowired
    private MyBean myBean;

    @Test
    public void mockStaticMethod() throws Exception {
        // Given
        final long expectedId = 2L;
        mockStatic(IdGenerator.class);
        when(IdGenerator.generateNewId()).thenReturn(expectedId);

        // When
        final Message message = myBean.generateMessage();

        // Then
        assertEquals(expectedId, message.getId());
        assertEquals("John Doe, Paul Anderson, Jane Doe", message.getContent());
    }

Even though the test looks exactly the same the internal implementation has a lot of differences. To have a look at the real code behind the to examples have a look in subversion here for the first example and here for the java agent example.

Round-up

Using the Java agent can help you out when you run into hard-to-resolve classloading issues with PowerMock. The implementation is still experimental and we are aware of some bugs and limitations. To know more and find help getting started with the agent please have a look at the documentation.

This Post Has 8 Comments

  1. Minsoo

    Thank you Johan. We could jump this hurdle with your notes. but we add org.w3c.* to PowerMockIgnore in addition to your recommendation.

  2. Deksa

    Hi Johan,

    if I use Spring’s @ContextConfiguration, it gives me
    “when() requires an argument which has to be ‘a method call on a mock'”
    although I have
    @PrepareForTest({Utils.class, Utils2.class})
    and
    PowerMockito.mockStatic(Utils.class);
    PowerMockito.when(Utils.staticMethod()).thenReturn(returnValue);
    PowerMockito.mockStatic(Utils2.class);
    PowerMockito.when(Utils2.staticMethod()).thenReturn(returnValue);

    On the other hand, if I use:
    @RunWith(PowerMockRunner.class)
    the application context xml configuration file is not taken into account (this I expect cause I am not using the spring runner @RunWith(SpringJUnit4ClassRunner.class) )

    It seems to me like it is buggy. Can you see if I am doing something wrong?

    Cheers,
    Deksa

  3. Deksa

    If I place the @Rule field
    I get “java.lang.IllegalStateException: Failed to load ApplicationContext”
    which is caused by
    “java.io.FileNotFoundException: class path resource [springApplicationContext.xml] cannot be opened because it does not exist”

    This is not an issue when I don’t use PowerMock. So I am guessing there is a bug somewhere. Correct me if I am wrong.

    Cheers,
    Deksa

  4. Katharina

    Thank you Johan. Your article allows us to test classes that extend Swing-Components by adding @PowerMockIgnore({“javax.swing.*”}).

  5. Oliver

    Katharina, if you can please provide details how you were able to test classes that extend Swing components? We’re also trying to unit test classes that extend Swing components, and have been unsuccessful in mocking Swing out. Every solution I’ve found is geared towards running the GUI, which is not what we need. Thanks!

  6. Oliver

    I figured it out.

    Swing cannot be mocked out completely. The combination of

    @PowerMockIgnore({ “javax.swing.*” })

    and calling

    UIManager.setLookAndFeel(new MockLookAndFeel());

    allows our unit tests to run. The mock look and feel class is as follows:

    public class MockLookAndFeel extends BasicLookAndFeel {
    @Override
    public String getDescription() {
    return “test”;
    }
    @Override
    public String getID() {
    return “testId”;
    }
    @Override
    public String getName() {
    return “test”;
    }
    @Override
    public boolean isNativeLookAndFeel() {
    return false;
    }
    @Override
    public boolean isSupportedLookAndFeel() {
    return true;
    }
    }

  7. lukepence

    hello
    when i use powermockrule , I got these exception, and how to fix it~?

    Security framework of XStream not initialized, XStream is probably vulnerable.

    com.thoughtworks.xstream.converters.ConversionException: Failed calling method
    —- Debugging information —-
    message : Failed calling method
    cause-exception : com.thoughtworks.xstream.converters.ConversionException
    cause-message : Failed calling method
    method : org.springframework.test.context.cache.DefaultContextCache$LruCache.readObject()
    class : org.springframework.test.context.cache.DefaultContextCache$LruCache
    required-type : java.security.CodeSource
    converter-type : com.thoughtworks.xstream.converters.reflection.SerializableConverter
    path : /org.powermock.modules.junit4.rule.PowerMockStatement$1/outer-class/fNext/next/next/next/testContextManager/testContext/cacheAwareContextLoaderDelegate/contextCache/contextMap/java.util.Collections$SynchronizedMap/default/m/map/org.springframework.web.context.support.GenericWebApplicationContext/classLoader/parent/defaultDomain/codesource/java.security.CodeSource
    line number : 37232
    class[1] : java.util.Collections$SynchronizedMap
    class[2] : org.springframework.test.context.cache.DefaultContextCache
    converter-type[1] : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
    class[3] : org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate
    class[4] : org.springframework.test.context.support.DefaultTestContext
    class[5] : org.springframework.test.context.TestContextManager
    class[6] : org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks
    class[7] : org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks
    class[8] : org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks
    class[9] : org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks
    class[10] : org.powermock.modules.junit4.rule.PowerMockStatement
    class[11] : org.powermock.modules.junit4.rule.PowerMockStatement$1
    version : 1.4.10
    ——————————-

Leave a Reply