It's easy to write your own JUnit test-runner. A developer on the java-platform often writes code in the context of some framework or container platform (Spring, Play, java-EE, OSGi, Guice, whatever) that provides facilities for application bootstrap and dependency injection. When at some point she wants to write a JUnit test suite for some subset of code the developer is faced with the problem of how to recreate the application framework's runtime context within JUnit's runtime context - which assumes a simple no-argument constructor for test-harness classes.
Fortunately - JUnit provides a serviceable solution for augmenting the test suite runtime context in the form of test runners. A developer uses an annotation on his test class to indicate that the tests require a custom runner. For example - Spring provides its SpringJUnit4ClassRunner which allows a test developer to use Spring annotations in her test suties - like this:
@RunWith(SpringJUnit4ClassRunner.class) class WhateverTest { @Service(name="whatever") Whatever service; @Test public void testSomething() { assertTrue( ... ); } ... }
The Littleware project includes a system that extends Guice with a simple module and bootstrap mechanism that I was able to integrate with JUnit with a simple LittleTestRunner that follows the same trick that Spring's test runner uses of simply extending JUnit's default BlockJUnit4ClassRunner - overriding the createTest method - easy cheesey:
package littleware.test; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import littleware.bootstrap.LittleBootstrap; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; /** * JUnit4 test runner enabled with littleware.bootstrap Guice bootrap and * injection. Imitation of SpringJunit4TestRunner: * */ public class LittleTestRunner extends BlockJUnit4ClassRunner { private static final Logger log = Logger.getLogger(LittleTestRunner.class.getName()); /** * Disable BlockJUnit4ClassRunner test-class constructor rules */ @Override protected void validateConstructor( List<Throwable> errors ) {} /** * Construct a new {@code LittleTestRunner} and initialize a * {@link LittleBootstrap} to provide littleware testing functionality to * standard JUnit tests. * * @param clazz the test class to be run * @see #createTestContextManager(Class) */ public LittleTestRunner(Class<?> clazz) throws InitializationError { super(clazz); if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "constructor called with [{0}]", clazz); } } /** * This is where littleware hooks in * * @return an instance of getClass constructed via the littleware managed Guice injector */ @Override protected Object createTest() { try { return LittleBootstrap.factory.lookup(this.getTestClass().getJavaClass()); } catch ( RuntimeException ex ) { log.log( Level.SEVERE, "Test class construction failed", ex ); throw ex; } } }
Now I can write tests in Guice's "inject dependencies into the constructor" style - like this:
/** * Just run UUIDFactory implementations through a simple test */ @RunWith(LittleTestRunner.class) public class UUIDFactoryTester { private final Provider<UUID> uuidFactory; /** * Constructor stashes UUIDFactory to run test * against */ @Inject() public UUIDFactoryTester(Provider<UUID> uuidFactory) { this.uuidFactory = uuidFactory; } /** * Just get a couple UUID's, then go back and forth to the string * representation */ @Test public void testUUIDFactory() { //... } }
No comments:
Post a Comment