Unit Testing Dilemma: Using a JNDI data source without running JBoss or Spring

11,125

Solution 1

There's a very simple answer to your problem, but you're not going to like it: Don't.

By definition, a unit test should verify the functionality of a single unit (the size of which may vary, but it should be self-sufficient). Creating a setup where the test depends upon web services, databases, etc. is counter-productive: It slows down your tests, it includes a gzillion of possible things that could go wrong (failed network connections, changes to data sets, ...) during the test, which have nothing to do with the actual code you are working on, and most importantly: It makes testing much, much harder and more complicated.

Instead, you should be looking for ways to decouple the legacy code from any data sources, so that you can easily substitute mock objects or similar test doubles while you are testing.

You should create tests to verify the integrity of your entire stack, but those are called integration tests, and they operate at a higher level of abstraction. I personally like to defer writing those until the units themselves are in place, tested and working - at least until you have come to a point where you no longer expect changes to service calls and protocols on a daily basis.

In your case, the most obvious strategy would be to encapsulate all calls to the web service in one or more separate classes, extract an interface that the business objects can depend on, and use mocks implementing that same interface for unit testing.

For example, if you have a business object that calls an address database, you should copy the JNDI lookup code into a new service class called AddressServiceImpl. Its public methods should mimic all the method signatures of your JNDI datasource. Those, then, you extract to the AddressService interface.

You can then write a simple integration test to verify that the new class works: Call all the methods once and see if you get proper results. The beauty of this is that you can supply a JNDI configuration that points to a test database (instead of the original one), which you can populate with test datasets to make sure you always get the the expected results. You don't necessarily need a JBoss instance for this (though I have never had any problems with the eclipse integration) - any other JNDI provider should work, as long as the data source itself behaves the same way. And to be clear: You test this once, then forget about it. At least until the actual service methods ever change.

Once you verified that the service is functional, the next task is to go through all the dependent classes and replace the direct calls to the datasource with calls to the AddressService interface. And from that point on, you have a proper setup to implement unit tests on the actual business methods, without ever having to worry about things that should be tested elsewhere ;)

EDIT

I second the recommendation for Mockito. Really good!

Solution 2

I had a very similar situation with some legacy code in JBoss AS7, for which refactoring would have been way out of scope.

I gave up on trying to get the datasource out of JBoss, because it does not support remote access to datasources, which I confirmed in trying.

Ideally though, you don't want to have your unit tests dependant on a running JBoss instance in order to run, and you really don't want them to have to run inside of JBoss. It would be counter to the concept of self-contained unit tests (even though you'll still need the database to be running :) ).

Fortunately, the initial context used by your app doesn't have to come from a running JBoss instance. After looking at this article referred to by an answer to another question, I was able to create my own initial context, populate it with my own datasource object.

This works without creating dependencies in the code because the classes under test typically run inside the container, where they simply do something like this to get the container-provided context:

InitialContext ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup(DATA_SOURCE_NAME);

They don't need to specify any environment to the constructor, because it has already been set up by the container.

In order for your unit tests to stand in for the container and provide a context, you create it, and bind a name:

InitialContext ic = new InitialContext();

// Construct DataSource
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setURL("url");
ds.setUser("username");
ds.setPassword("password");

ic.bind(DATA_SOURCE_NAME, ds);

This needs to happen in each test class's @BeforeClass method.

Now the classes being tested get my initial context when running in unit tests, and the container's when deployed.

Share:
11,125
Laura
Author by

Laura

C++, C#, Java, SQL

Updated on June 05, 2022

Comments

  • Laura
    Laura almost 2 years

    Problem Statement

    I want to be able to run junit tests on methods that connect to a database.

    Current setup

    Eclipse Java EE IDE – Java code is using no framework. The developers (me included) want more robust testing of current legacy code BEFORE attempting to move the code into a Spring framework so that we can prove along the way that the behavior is still correct.

    JBoss 4.2 – Version limitation by vendor software (Adobe LiveCycle ES2); Our Java web application uses this setup of JBoss to run and makes use of the Adobe LiveCycle API.

    We have been unable to successfully run the vendor configured JBoss within Eclipse – we have spent weeks attempting this, including contacting the company that provides our support for the configuration of JBoss for Adobe LiveCycle. Supposedly the problem is a memory limitation issue with settings in Eclipse, but changing the memory settings has thus far failed in a successful JBoss server start within Eclipse. For now, attempting to get JBoss to run inside of Eclipse is on hold.

    The database connection is defined in a JNDI data source that JBoss loads on start up. Both our web application and Adobe LiveCycle need to create connections to this data source.

    Code

    I am glossing over error checking and class structure in this code snippet to focus on the heart of the matter. Hopefully that does not cause problems for others. Text in square brackets is not actual text.

    Our code to create the connection is something like this:

    Properties props = new Properties();
    FileInputStream in = null;
    in = new FileInputStream(System.getProperty("[Properties File Alias]"));
    props.load(in);
    String dsName = props.getProperty(“[JNDI data source name here]”); 
    InitialContext jndiCntx = new InitialContext();
    DataSource ds = (DataSource) jndiCntx.lookup(dsName);
    Ds.getConnection();
    

    I want to be able to test methods dependent upon this code without making any changes to it.

    Reference to properties file alias in properties-service.xml file:

      <!-- ==================================================================== -->
      <!-- System Properties Service                                            -->
      <!-- ==================================================================== -->
    
      <!-- Allows rich access to system properties.-->
    
    <mbean code="org.jboss.varia.property.SystemPropertiesService" 
     name="jboss:type=Service,name=SystemProperties">
      <attribute name="Properties">
        [Folder Alias]=[filepath1]
        [Properties File Alias]=[filepath2]
      </attribute>
    </mbean>
    

    Snippet from properties file located at filepath2

    [JNDI data source name]=java:/[JNDI data source name]
    

    The JNDI xml file for this data source is set up like this:

    <datasources>
      <local-tx-datasource>
        <jndi-name>[JNDI data source name here]</jndi-name>
        <connection-url>jdbc:teradata://[url]/database=[database name]</connection-url>
        <driver-class>com.teradata.jdbc.TeraDriver</driver-class>
        <user-name>[user name]</user-name>
        <password>[password]</password>
        <!-- sql to call on an existing pooled connection when it is obtained from pool -->
        <check-valid-connection-sql>SELECT 1+1</check-valid-connection-sql>
      </local-tx-datasource>
    </datasources>
    

    My thoughts of where the solution may be

    Is there something I can do in a @BeforeClass method in order to make the properties the above code is looking for available without JBoss? Maybe somehow using the setProperty method of the java.util.Properties class? I would also like to use the same JNDI xml file that JBoss reads from, if possible, in order to reduce duplicate configuration settings.

    So far all of my research ends with the advice “Use Spring”, but I don’t think we’re ready to open that can of worms yet. I am not an expert in JBoss, but if more details of our JBoss setup are needed for a helpful answer, I will do my best to get them, though I will likely need some pointers on where to look.

    Stackoverflow Research references:
    Jndi lookup in junit using spring
    Out of container JNDI data source
    Other research references:
    http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Properties.html
    http://docs.oracle.com/javase/jndi/tutorial/basics/prepare/initial.html

  • Laura
    Laura over 11 years
    I am selecting this response due to the help between the distinction of unit testing and integration testing and the good strategic advice. I am still comparing mockito and JMockit (mentioned by Rogerio). Even mockito's documentation suggests that JMockit may be the better choice for certain types of legacy code. I have some analysis work ahead of me. Thank you, stackoverflow!
  • Powerslave
    Powerslave almost 6 years
    Very nice answer! A few small remarks, though: Avoid names like *Impl or Default*. Such names are not informative and they often indicate a design flaw (e.g. you've abstracted something other than you should have). You'd rather want to name your subtypes according to their core implementation characteristic, e.g. StaticAddressService, JpaAddressService, ExternalHttpAddressService, etc. Also, instead of using JNDI like a hardcoded new call, you're probably better off placing those calls into initialization code, practically turning/merging it into a neat, standard case of DI.