Friday, October 22, 2010

Performing Unit Tests with the @Inject Container as a Runner

As a response to the following post about the "Perfect Integration Test" i would like to give my point of view of what a perfect test with active connection to a database is. Jboss Blogg entry

The following has a big difference to the Arquillian project, it never intends to be able to execute the same testsuites on different container vendors. Its more focused on creating the fastest and simplest possible UnitTest for your EJB code.

But I must admit that a lot of my inspiration to do this came from Arquillian, i just thought they had the wrong focus.

I will explain how its possible to perform a UnitTest where the following rules are enforced.

  1. The services are "wired" for EJB3 (Stateless/Stateful Sessions Beans supported) automatically with a scanning path configuration.
  2. For each test the "container" is automatically reset
  3. For each test the internal database is rolled back
  4. Its possible to register mocking services by overriding existing configuration, to make it easier to vary the test-suite without reconfoguring the entire software with a new "config class".


All these feature's are enabled with the help of the hrodberaht injection container and an extension to it called ejbunit (might be renamed to just junit as i am thinking about adding spring support as well, as it feels easy enough).
Injection Wiki (the 1.2-SNAPSHOT version though is needed, as its in this version that the cloning support is added)

This is done using the followign techniques.

1. The services are "wired" for EJB3 (Stateless/Stateful Sessions Beans supported) automatically.
- All that is needed is a JUnitRunner and a Project Configuration (scanners are supported)

2. For each test the "container" is automatically reset
- The container is resets to its original state before the test is executed. Original is whatever the config found in the class Project Configuration context.

3. For each test the internal database is rolled back at the end
- All calls to close/commit are prevented (silently) and a rollback is executed once the testsuite is over.
- Works as long as the datasource is created by the Project Configuration

4. Its possible to register mocking services by overriding existing configuration, to make it easier to vary the testsuite without reconfoguring the entire software with a new "config class".
- Is done using a utility that can reach the running @Inject Container for the active test. (supports several active test threads if needed)

So how does this look code wise:
The test
@EJBContainerContext(EJBContainerConfigExample.class)
@RunWith(EJBJUnitRunner.class)
public class TestEJB3ServiceContext {

    @EJB
    private EJB3ServiceInterface ejb3ServiceInterface;

    @Test
    public void testEJBWiring(){
        String something = ejb3ServiceInterface.findSomething(12L);
        assertEquals("Something", something);

        String somethingDeep = ejb3ServiceInterface.findSomethingDeep(12L);
        assertEquals("Something Deep", somethingDeep);
    }

    @Test
    public void testEJBResourceInjectionAndUpdate(){
        String something = anInterface.findSomethingDeepWithDataSource(12L);
        assertEquals("The Name", something);
        anInterface.updateSomethingInDataSource(12L, "A new Name");
        something = anInterface.findSomethingDeepWithDataSource(12L);
        assertEquals("A new Name", something);
    }

    @Test
    public void testEJBWiringWithMockito(){

        EJB3InnerServiceInterface anInterface =
            Mockito.mock(EJB3InnerServiceInterface.class);
        Mockito.when(anInterface.findSomething(12L))
            .thenReturn("Something Deep FormMock");
         EJBResourceHandler
             .registerServiceInstance(EJB3InnerServiceInterface.class, anInterface);

        EJB3ServiceInterface serviceInterface =
             EJBResourceHandler.getService(EJB3ServiceInterface.class);
        String something = serviceInterface.findSomething(12L);
        assertEquals("Something", something);
        String somethingDeep = serviceInterface.findSomethingDeep(12L);
        assertEquals("Something Deep FormMock", somethingDeep);
    }
}

So what is all this:
1. The config @EJBContainerContext(EJBContainerConfigExample.class)
- This contains the code that "scans" for all EJB's in the software, using package scanning. This is similar to the annotation @Deployment used by Arquillian.

public class EJBContainerConfigExample extends EJBContainerConfigBase {

    public EJBContainerConfigExample() {
        String dataSourceName = "DataSource";
        addResource(dataSourceName,
             ResourceCreator.createDataSource(dataSourceName));
        addSQLSchemas(dataSourceName,
            "test/org/hrodberaht/inject/extension/ejbunit");
    }

    @Override
    public InjectContainer createContainer() {
        return createAutoScanEJBContainer(
             "test.org.hrodberaht.inject.extension.ejbunit.ejb3.service");
    }


- "addResource" Will register a  datasource for the application named "DataSource", this works as any datasource and can be injected with @Resource as regular with EJB3.
- "addSQLSchemas" Will try to execute all SQL files found in this directory names with "create_schema*" once all those are executed all file with the name "insert_data*" will be executed. This execution results will be commited to the datasource selected in the method call.
-  "createContainer" A container will be created and returned (this is used by the Runner)

2. @RunWith(EJBJUnitRunner.class)
This will take care of all the gritty details with rollback of the active connection and reset of the "container".
Before a testsuite is executed a clone of the container is made and registered as the active container for the running test (after the original has been created from the Configuration).

3. @EJB private EJB3ServiceInterface ejb3ServiceInterface;
All testclasses that have the RunWith can have injected Service (both @EJB and @Inject works)

4. The tests themselves
Are not that special, only that they can be be executed without any before/after handling for rollback or cleanup of the database.
4.1 testEJBWiring
The test for this will only verify that the injection have done what they are suppose to.
4.2 testEJBResourceInjectionAndUpdate
The test will verify that
- the SQL scripts have been executed (first row is a selection of the id 12)
- the Datasource has been injected correctly to the inner service bean
- that the update code works as intended and can be refetched from the running database.

Note: In the real test suite a second test is execute to verify that the update is automatically rolled
back by the testrunner.

All this code can be found in extensions to the hrodberaht @Inject. At the google code projects.
http://code.google.com/p/java-simple-utils/
http://code.google.com/p/injection-extensions/

The code for the test case used as example here can be found at:
http://code.google.com/p/injection-extensions/source/browse/trunk/ejbunit/src/test/java/test/org/hrodberaht/inject/extension/ejbunit/ejb3/TestEJB3ServiceContext.java

A simple example POM file for anyone that want to test this has been committed to the test suite.
http://code.google.com/p/injection-extensions/source/browse/trunk/ejbunit/example-pom/pom.xml
PS: I have not verified the POM on another computer so please report any problems if you try it.

1 comment:

resume in canada said...

well, i was searching for some good techniques for unit testing. you share some very nice and straight forward techniques here. your effort is really appreciated. thanks and keep doing the same