Monday, August 9, 2010

JNDI, SQL, and JUnit

So I recently had a problem that I would've assumed many people in Java land have had. I have some code that will live in an app container (Tomcat, JBoss, or Weblogic probably), and I want to write unit tests for it.

Here is what the code does.
  • Get a database connection using JNDI (from the app container)
  • Does some database stuff
Here are my self imposed goals for the unit tests:
  • Runnable out of the box. (i.e. if you can checkout and build the project, you can run the unit tests)
  • Does not require an app container. I don't want to require a running instance of JBoss just to have access to JNDI so I can run unit tests.
  • Each developer has their own unit testing database sandbox to run unit tests in, so they won't/can't collide with each other.
  • Ideally the unit testing database is checked into the repository.
These seemed like reasonable goals to me, and I figured a few minutes of Google searching would give a couple of options of how this is typically done. Since it took more than that, I'll write up what I did.

HSQLDB

StackOverflow led me to HSQLDB. With HSQLDB I am able to get an in-memory database, which is perfect for unit testing. And I can make sure all other team members can use it, just by including the jar file in the check-in. Well, that seems easy enough.

JNDI

Now all I had to do was to make the HSQLDB DataSource available via JDBC. This should be easy enough, right? Right? Ugh.

I found this blog that seems to be the standard way to inject a JNDI datasource without an app container. Seems easy enough, except that when I tried to follow the directions, I got a ClassNotFoundException for org.apache.naming.java.javaURLContextFactory. It seems that this class is included with Tomcat. I suppose I could've gone and gotten the Tomcat jar to include in the project, but this didn't feel right with my goal to be app container agnostic.

So with a little more searching I found an in-memory JNDI context on SourceForge. Well, that seems to be what I want.

[edit - As I wrote in my follow up post, Don't Reuse. Rewrite!, I ended up writing my own in memory JNDI context, since all I needed were the bind and lookup methods.]

Huh - now that I write it up, it seems much less painful than it was when I was trying to track all this down myself.

Implementation Summary

Download two packages:
I added 3 jars to my CLASSPATH.
  • hsqldb.jar - comes with the HSQLDB download.
  • jndi.jar - comes with the in-memory JNDI context. [edit - don't use any more]
  • util.jar - also with the in-memory JNDI context. (and yes - they really did name the jar file unit.jar) [edit - don't use any more]
As for the actual unit testing code, I am using JUnit 4. This means that I can put the setup code once per class. As such my code looks something like:
  1 import java.sql.Connection;
2 import java.sql.Statement;
3
4 import javax.naming.Context;
5 import javax.naming.InitialContext;
6 import javax.sql.DataSource;
7
8 import jndi.naming.provider.MemoryContextFactory;
8 import mypackage.MockInitialContextFactory;
9
10 import org.hsqldb.jdbc.JDBCDataSource;
11 import org.junit.BeforeClass;
12
13 public class MyUnitTest {
14 private static final String JNDI_NAME="my_JNDI_name";
15
16 @BeforeClass
17 public static void setUpBeforeClass() throws Exception {
18 createJNDIContext();
19 createDBTable();
20 }
21
22 public static void createJNDIContext() throws Exception {
23 JDBCDataSource ds = new JDBCDataSource();
24 ds.setDatabase("jdbc:hsqldb:mem:mymemdb");
25 ds.setUser("SA");
26
27 System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
28 MemoryContextFactory.class.getName());
28 MockInitialContextFactory.class.getName());
29 InitialContext ic = new InitialContext();
30 ic.bind(JNDI_NAME, ds);
31 }
32
33 private static void createDBTable() throws Exception {
34 DataSource ds = (DataSource)new InitialContext().lookup(JNDI_NAME);
35 Connection conn = ds.getConnection();
36 String create = "CREATE TABLE MY_TABLE ("
37 // Insert column description here.
38 + " )";
39 Statement stmt = conn.createStatement();
40 stmt.executeUpdate(create);
41 conn.close();
42 }
43
44 // Unit Tests go here
45 }
46
Note - this code was colorized by: http://puzzleware.net/codehtmler/default.aspx.


No comments: