Mocking J

Published on 3 August 2012
This post image

Although I'm a perennial test-driven development (TDD) wonk, I've been surprised by the recent interest in my xUnits, many of which are so pedestrian I've completely forgotten about them. After all, once the code is written and shipped, you can often ignore unit tests as long as they pass on builds and you aren't updating the code under test (refactoring, extending, whatever). Along with that interest has come discussions of mock frameworks.

Careful... heavy reliance on mocks can encourage bad practice. Classes under test should be so cohesive and decoupled they can be tested independently with little scaffolding. And heavy use of JUnit for integration tests is a misuse of the framework.

But we all do it. You're working on those top service-layer classes and you want the benefits of TDD there, too. They use tons of external resources (databases, web services, files, etc.) that just aren't there in the test runner's isolated environment. So you mock it up, and you want the mocks to be good enough to be meaningful. Mocks can be fragile over time, so you should also provide a way out if the mocks fail but the class under test is fine. You don't want future maintainers wasting time propping up old mocks.

So how to balance all that? Here's a quick example to illustrate a few techniques.

public class MyServiceTest {
	private static Log logger = LogFactory.getLog(MyServiceTest.class);
	private MyService myService = new MyService();	                 // #1
	private static boolean isDatabaseAvailable = false;

	@BeforeClass
	public static void oneTimeSetUp() throws NamingException   {
		// Set up the mock JNDI context with a data source.
		DataSource ds = getDataSource();
		if (ds != null) {                                        // #2
			isDatabaseAvailable = true;
			SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
			builder.bind("jdbc/MY_DATA_SOURCE", ds);
			builder.activate();
		}
	}

	@Before
	public void setUp() {
		// Ignore tests here if no database connection
		Assume.assumeTrue(isDatabaseAvailable);                 // #3
	}

	@Test
	public void testMyServiceMethod() throws Exception {
		String result = myService.myServiceMethod("whatever");
		logger.trace("myServiceMethod result: " + result);      // #4
		assertNotNull("myServiceMethod failed, result is null", result);
		// Other asserts here...
	}
}

Let's take it from top to bottom (item numbers correspond to // #x comments in the code):

  1. Don't drag in more than you need.  If you're using Spring, you may be tempted to inject (@Autowire) the service, but since you're testing your implementation of the service, why would you?  Just instantiate the thing. There are times when you'll want a Spring application context and, for those, tools like @RunWith(SpringJUnit4ClassRunner.class) come in handy. But those are rare, and it's best to keep it simple.

  2. Container? forget it!  Since you're running out of container, you will need to mock anything that relies on things like JNDI lookups. Spring Mock's SimpleNamingContextBuilder does the job nicely.

  3. Provide a way out.  Often you can construct or mock the database content entirely within the JUnit using in-memory databases like HSQLDB. But integration test cases sometimes need an established database environment to connect to. Those cases won't apply if the environment isn't there, so use JUnit Assume to skip them.

  4. Include traces.  JUnits on business methods rarely need logging, but traces can be valuable for integration tests. I recommend keeping the level low (like debug or trace) to make them easy to throttle in build logs.

Frameworks like JMockit make it easy to completely stub out dependent classes. But with these, avoid using so much scaffolding that your tests are meaningless or your classes are too tightly coupled.

Just a few suggestions to make integration tests in JUnits a bit more helpful.