Singleton returning new instance when accessed fro

2019-09-18 07:11发布

问题:

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.