Search code examples
javajunitsingletonprivate-membersjmockit

JMockit: Singleton class, Order of test


I have a Singleton class to test:

public class Singleton {
    private static Singleton instance;

    private List<String> list;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    public boolean methodOne() {
        if (list == null) {
            list = new ArrayList<String>();
            list = SomeClass.fillListOne();
        }
        return SomeClass.verifyList(list);
    }

    public boolean methodTwo() {
        if (list == null) {
            list = new ArrayList<String>();
            list = SomeClass.fillListTwo();
        }
        return SomeClass.verifyList(list);
    }
}

With the following test class:

@RunWith(JMockit.class)
public class SingletonTest {
    @Test
    public void testOne(final @Mocked SomeClass someClass) {
        Singleton.getInstance().methodOne();
        new Verifications() {
            {
                SomeClass.fillListOne();
            }
        };

    }

    @Test
    public void testTwo(final @Mocked SomeClass someClass) {
        Singleton.getInstance().methodTwo();
        new Verifications() {
            {
                SomeClass.fillListTwo();
            }
        };
    }
}

If I execute only "testOne" or only "testTwo", the tests pass. If I execute all tests, it passes only the first method executed. How can I set "list" attribute to null, for instance in a @Before method? How to use Deencapsulation with Singleton or private member without setters?


Solution

  • That class is not particularly susceptible to testing. I'd urge you to rewrite it if you have the option to. (Testing with mocked static method calls is a maintenance nightmare, and I also have a general dislike of all things singleton-y -- they seem to be used everywhere whether they should be or not).

    Regardless, the documentation suggests you could do something like this (I also added an extra field for readability, though you don't have to):

    @RunWith(JMockit.class)
    public class SingletonTest {
        private Singleton instance;
    
        @Before
        public void initialise() {
            Deencapsulation.setField(Singleton.class, "instance", null);
            instance = Singleton.getInstance();
        }
    
        @Test
        public void testOne(final @Mocked SomeClass someClass) {
            instance.methodOne();
            new Verifications() {
                {
                    SomeClass.fillListOne();
                }
            };
        }
        // ...other tests...
    }