Question

I'm working with a large test suite for an old Java codebase. Long story short, it uses DBUnit to upload static read-only datasets from the local harddisk. At present this is being done on the per-test level, which means the suite takes a ridiculously long time to run.

I'm trying to make a shared static class to be shared at the suite-level. (We also didn't have a proper test suite defined -- I made one using ClasspathSuite)

Another wrinkle is that all of are tests are using @RunWith(PowerMockRunner.class) -- so there's occasionally classpath issues mucking up what I think would normally solve things.

Here's a simple case of what's not working.


Java Code Under Test

Static Dependency in Codebase

package com.somecorp.proj;
public class SomeDependency {
    public static String getStaticString() {
        // some resource intensive process we don't want running in unit tests
        return "real value";
    }
}

Class Under Test 1

package com.somecorp.proj;

public class UnderTest {

    public String getIt() {
        return "Here is the value: " + SomeDependency.getStaticString();
    }
}

Class Under Test 2

package com.somecorp.proj;

public class AlsoUnderTest {
    public String getTheThing() {
        return "some other value using it: " + SomeDependency.getStaticString();
    }
}

JUnit Code

Code with init method I want run only ONCE at the start of the suite run

package com.somecorp.proj.testClasses;

public class StaticTestClassRequiringInitialization {

    private static String testString;

    public static void init() {
        // Some expensive stuff
        System.out.println("EXPENSIVE INITIALIZATION");
        testString = "a test string";
    }

    public static String getTestString() {
        return testString;
    }
}

Test 1

package com.somecorp.proj;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
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;

import com.somecorp.proj.testClasses.StaticTestClassRequiringInitialization;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SomeDependency.class)
public class TestUnderTest {

    @Before
    public void setUp() {
        PowerMockito.mockStatic(SomeDependency.class);
        PowerMockito.when(SomeDependency.getStaticString()).
            thenReturn(StaticTestClassRequiringInitialization.getTestString());
    }

    @Test
    public void testGetIt() {
        UnderTest ut = new UnderTest();
        assertEquals(
            "Here is the value: a test string",
            ut.getIt()
        );
    }
}

Test 2

package com.somecorp.proj;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
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;

import com.somecorp.proj.testClasses.StaticTestClassRequiringInitialization;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SomeDependency.class)
public class TestAlsoUnderTest {

    @Before
    public void setUp() {
        PowerMockito.mockStatic(SomeDependency.class);
        PowerMockito.when(SomeDependency.getStaticString()).
            thenReturn(StaticTestClassRequiringInitialization.getTestString());
    }

    @Test
    public void testGetTheThing() {
        AlsoUnderTest ut = new AlsoUnderTest();
        assertEquals(
            "some other value using it: a test string",
            ut.getTheThing()
        );
    }
}

Test Suite

package com.somecorp.proj;

import static org.junit.extensions.cpsuite.SuiteType.RUN_WITH_CLASSES;
import static org.junit.extensions.cpsuite.SuiteType.TEST_CLASSES;

import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.extensions.cpsuite.ClasspathSuite.BeforeSuite;
import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
import org.junit.extensions.cpsuite.ClasspathSuite.SuiteTypes;
import org.junit.runner.RunWith;

import com.somecorp.proj.testClasses.StaticTestClassRequiringInitialization;

@RunWith(ClasspathSuite.class)
@SuiteTypes({RUN_WITH_CLASSES, TEST_CLASSES})
@ClassnameFilters({".*Test.*"})
public class ProjectJUnitSuite {

    @BeforeSuite
    public static void setUpBeforeSuite() {
        StaticTestClassRequiringInitialization.init();
    }

}

JAR details

  • powermock-mockito-1.4.12-full.jar
  • mockito-all-1.9.0.jar
  • cpsuite-1.2.6.jar
  • javassist-3.16.1-GA.jar
  • Using Junit 4.8.1

And the trace of the test failure (notably not an error - a failure) (for one test...2nd one is pretty much identical):

org.junit.ComparisonFailure: expected:<...her value using it: [a test string]> but was:<...her value using it: [null]>
    at org.junit.Assert.assertEquals(Assert.java:123)
    at org.junit.Assert.assertEquals(Assert.java:145)
    at com.somecorp.proj.TestAlsoUnderTest.testGetTheThing(TestAlsoUnderTest.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:60)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
    at java.lang.reflect.Method.invoke(Method.java:611)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:312)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:296)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:112)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:73)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:284)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:209)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.junit.extensions.cpsuite.ClasspathSuite.run(ClasspathSuite.java:196)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

How might I get this shared static initializer to run once per suite and be referenceable from all of my Powermock-enabled unit tests?

Was it helpful?

Solution

I got the initialization to occur only once by using the @PowerMockIgnore annotation on all the test classes referencingStaticTestClassRequiringInitialization.

In this case in particular adding the below annotation to both of the JUnit test classes would do the trick.

@PowerMockIgnore("com.somecorp.proj.testClasses.StaticTestClassRequiringInitialization")

I had tried this before, and it had not initially worked because the arguments I had initially passed had been either:

  • Too broad - Classes that needed to be mocked were no longer being loaded by powermock classloader because I passed an entire package to PowerMockIgnore
  • Not broad enough - A few dependencies more complex than the example above were present requiring me to ignore some other classes. The result were some strange class loading mismatch errors.

This won't work in all cases, (in particular it won't work if the StaticTestClassRequiringInitialization uses classes that are also being mocked in the test), but it does work in this case.

I had also done some investigation into the PowerMockAgent to avoid the PowerMockRunner, and therefore many of the associated Classloading issues altogether, but the production code under test* required suppressing static initalizers, so it was a non-starter.

*Apologies for not sharing the full source, such are the perils of asking questions for large proprietary codebases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top