Handling System Properties

It sometimes happens that a class will manipulate or depend on system properties. Since we always want to restore conditions to what they were before a unit test, SimpleStub provides support for manipulating them in a reversible way.

One place where this can be useful is JNDI, where the call to new InitialContext() uses a factory defined by the property java.naming.factory.initial, defined by the constant javax.naming.Context.INITIAL_CONTEXT_FACTORY. Testing code that uses JNDI can often be simplified is we can substitute a test factory.

The first thing we do is to define an appropriate Context stub class. In this case, our test will only do lookups, so that is all that we implement:

abstract static class ContextStub implements javax.naming.Context {
    private static  Map<String, Object> map = new HashMap<>();

    static void clear() { map.clear(); }

    static void defineEntry(String name, Object value) { map.put(name, value); }

    @Override
    public Object lookup(String name) throws NamingException {
        if (!map.containsKey(name)) throw new NamingException("failed in test");
        return map.get(name);
    }
}

Then we define the initial context factory itself, along with methods to activate it and clean up afterwards.

static class InitialContextFactoryStub implements javax.naming.spi.InitialContextFactory {
    private static  Memento memento = Memento.NULL;

    static void install() {
        ContextStub.clear();
        memento = SystemPropertySupport.install(Context.INITIAL_CONTEXT_FACTORY, InitialContextFactoryStub.class.getName());
    }

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

    @Override
    public Context getInitialContext(Hashtable<?,?> environment) throws NamingException {
        return createStub(ContextStub.class);
    }

}

Note that when we install the factory, we also clear all current definitions. This prevents one test from affecting others. Now we can use this in a test:

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

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

    @Test
    public void whenJndiValueIsString_reportValue() {
        ContextStub.defineEntry(computer.VALUE_NAME, "zork");

        assertThat(computer.getValue(), is("zork"));
    }

    @Test
    public void whenJndiValueIsBoolean_reportValue() {
         ContextStub.defineEntry(computer.VALUE_NAME, Boolean.TRUE);

         assertThat(computer.getValue(), is("asserted"));
     }

thus verifying that the getValue method is looking up the value named VALUE_NAME and converting it appropriately.