问题
I am using Junit 4.12 with PowerMock 1.6 with Mockito. I have also used PowerMockRule library as described here. I am trying to execute initialization code for all of my test cases exactly once as described in this SO Thread. Its executing the initialization code exactly one time however, if I do ServiceInitializer.INSTANCE
inside test method it returns me new object. I am not able to understand this behavior. Does anyone have any idea why this is happening? If I execute my code without PowerMockRule Library and run my test with PowerMockRunner then it works fine but in that case my ClassRule is not getting executed.
public class ServiceInitializer extends ExternalResource {
public static final TestRule INSTANCE = new ServiceInitializer();
private final AtomicBoolean started = new AtomicBoolean();
@Override protected void before() throws Throwable {
if (!started.compareAndSet(false, true)) {
return;
}
// Initialization code goes here
System.out.println("ServiceInitializationHelper:"+this); //Print Address @3702c2f1
}
@Override protected void after() {
}
}
class BaseTest{
@Rule
public PowerMockRule powerMockRule = new PowerMockRule();
@ClassRule
public static final TestRule serviceInitializer = ServiceInitializer.INSTANCE;
@Before
public final void preTest() {
// some code
}
@After
public final void postTest() {
//some code
}
}
@PrepareForTest({MyClass.class})
public class MyTest extends BaseTest {
@Test
public void testMethodA_1(){
System.out.println(ServiceInitializer.INSTANCE);//Print Address @54d41c2b
}
}
Update
I printed the classloader for the classes and it turns out for first print statement the classloder was sun.misc.Launcher$AppClassLoader
and for the second print statement the classloder was org.powermock.core.classloader.MockClassLoader
. How can I solve this?
回答1:
You don't have a singleton. You have a static INSTANCE variable. Keep in mind that one of those can exist for every classloader you have.
Instead make an enum of ServiceInitializer, like so
public enum ServiceInitializer {
INSTANCE;
// rest of class goes here
}
And rely on the JVM's language contracts to ensure the singleton.
Or, better yet, write your code to handle situations where more than one ServiceInitializer can exist, but it just happens that your program only uses one instance. This is the ideal choice, allowing you to alternate between the real ServiceInitializer and a mock if desired.
回答2:
Edwin is correct; this is an issue with PowerMock creating a new ClassLoader for every test. I strongly recommend refactoring your code so it can be tested without PoeerMock and switch to Mockito.
These books may be helpful
- Working Effectively With Legacy Code
- Refactoring to Patterns
In the mean time, you can reference ServiceInitializer
from your base class:
public class ServiceInitializer extends ExternalResource {
public static final ServiceInitializer INSTANCE = new ServiceInitializer();
private final AtomicBoolean started = new AtomicBoolean();
@Override protected void before() throws Throwable {
if (!started.compareAndSet(false, true)) {
return;
}
// Initialization code goes here
System.out.println("ServiceInitializationHelper:"+this);
}
@Override protected void after() {
}
}
class BaseTest{
@Rule
public PowerMockRule powerMockRule = new PowerMockRule();
@ClassRule
public static final ServiceInitializer serviceInitializer = ServiceInitializer.INSTANCE;
@Before
public final void preTest() {
// some code
}
@After
public final void postTest() {
//some code
}
}
@PrepareForTest({MyClass.class})
public class MyTest extends BaseTest {
@Test
public void testMethodA_1(){
System.out.println(serviceInitializer);
}
}
回答3:
Well I finally found the work around for this problem. As explained in my question my class was getting loaded by two different class loaders and thus causing problems for me. In order to resolve my issue I used @PowerMockIgnore
annotation in order to defer its loading as follows:
@PowerMockIgnore({"com.mypackage.*"})
class BaseTest{
// Stuff goes here
}
This annotation tells PowerMock to defer the loading of classes with the names supplied to value() to the system classloader. You can read about this annotation from here.
来源:https://stackoverflow.com/questions/34601866/singleton-returning-new-instance-when-accessed-from-test-method