Reputation: 51
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.
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();
}
}
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
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?
Upvotes: 4
Views: 2380
Reputation: 51
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:
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.
Upvotes: 1