Handling Static Variables

Static variables can represent a bit of a challenge in unit testing. They are effectively global state, which means that we cannot always predict how they will change; in addition, they are typically private, which limits our ability to inject them into the SUT.

Ultimately, we want to refactor our code to take any such values as injected dependencies; however, we need to start by testing the code as it exists. Java does allow us to cheat via reflection, and the usage tends to the same from test to test: preserve the old value, and replace it with a test value before the test, and then restore it afterwards. This is a common pattern, and the StaticStubSupport class exists to encapsulate that behavior.

The following example tests some behavior of the Light class. After the system has been running for ten seconds, but not fewer, its isWarm() method should return true. Time since startup is determined by calling the getSecondsSinceStartup() of the Timer interface; however, the implementation of that interface is obtained by calling a static method: TimeManager.getTimer() which returns the static variable, timer, created by some other code during system startup.

The first thing we do is to create an appropriate stub class:

    abstract static class TimerStub implements Timer {
        private long secondsSinceStartup = 0;
        private static Memento memento = Memento.NULL;

        static void install() throws NoSuchFieldException {
            memento = StaticStubSupport.install(TimerManager.class, "timer", createStub(TimerStub.class));
        }

        static void uninstall() {
            memento.revert();
        }

        static void setSecondsSinceStartup(long seconds) { secondsSinceStartup = seconds; }

        @Override
        public long getSecondsSinceStartup() { return secondsSinceStartup; }
    }

In addition to providing a test implementation of our method, and a way to give it a value, we've also created a pair of methods to install and uninstall the stub itself, using StaticStubSupport. Now we can use this in a test:

    @Before
    public void setUp() throws Exception {
        TimerStub.install();
    }

    @After
    public void tearDown() {
        TimerStub.uninstall();
    }

    @Test
    public void afterNineSeconds_lightNotWarm() {
        TimerStub.setSecondsSinceStartup(9);
        assertFalse(light.isWarm());
    }

    @Test
    public void afterTenSeconds_lightIsWarm() {
        TimerStub.setSecondsSinceStartup(10);
        assertTrue(light.isWarm());
    }

thus verifying that the isMature method is reading the Timer and changing state once ten seconds have elapsed.