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.