Project

General

Profile

Actions

Testing java code using Maven and Unitils

Related pages:

Configuration files

There are two Unitils configuration files in the cdmlib project which contain among other stuff the database connection and the like for the Unitils !DatabaseModule and !DbUnitModule:

  • cdmlib-io: /cdmlib-io/src/test/resources/unitils.properties

  • cdmlib-persistence: /cdmlib-persistence/src/test/resources/unitils.properties

HINTS: (see also #3293)

  1. To access the H2 data base you have to start the server at some point in the code and you have to set a breakpoint of course because the data base only exists during the runtime of the test.

  2. You can have a look into the H2 data base by starting the H2 web server

org.h2.tools.Server.createWebServer(new String[]{}).start();

which is then available under localhost:8082.


Everything below this line may be OUTDATED and needs to be reviewed.


If we're following good test driven design practices, then we should be writing tests before we write code and running them often to demonstrate that our code works. It is important that tests:

  • Run fast (they have short setups, run times, and break downs).

  • Run in isolation (you should be able to reorder them).

  • Use data that makes them easy to read and to understand.

  • Use real data (e.g. copies of production data) when they need to.

  • Represent one step towards your overall goal.

Maven gets us some of the way towards this by providing a standard build cycle, integration with surefire, a testing harness that isolates the classes under test in their own boot loader to ensure that only dependencies specified are present in the test environment, dependency management, etc.

Nevertheless, it is still easy to write tests that don't follow the principles outlined above. One way to make your life easier is to use some libraries to provide boilerplate code (i.e. code which you're likely to use from one test to the next) to make it simpler and easier to do complicated things within your test. One really interesting tool is Unitils which is a meta-framework which ties several testing api's together. An example of a simple unit test using unitils is as follows:

public class SimpleTest extends UnitilsJUnit4 {

    private ClassUnderTest classUnderTest;
    private String testData;

    @Before
    public void setup() {
        classUnderTest = new ClassUnderTest();
        testData = "Lorem";
    }

    @Test
    public void testMethod() {
        String result = classUnderTest.method(testData);

        assertEquals("Ipsum",result);
    }  
} 

There is one thing which is worth noticing about the test above – it sets up test data outside the test itself. There are two reasons for this. Firstly, its better to set up the objects needed and the data required outside of the test itself if you can. It makes the test easier to read, and by using @@Before@, which is run prior to every test, the test data is restored to the same state at the beginning of each test. This isn't always possible, but if you need to initialize many objects prior to a test, this might mean your test is too complicated and needs to be broken down further.

Unitils only really begins to show a benefit, however, when you want to do something more ambitious than a straightforward unit test.

Testing persistence-related functionality

If we want to test the persistence layer in the cdm properly, we need spring (our DAO's are spring beans), hibernate (our favourite ORM tool), and a test database with data in it. We want to be able to insert data into the database before we run a test, and if we modify the state of the database during a test, we want to check its state afterwards. DBUnit is a tool to help us do this, and unitils makes it easy to write tests using dbunit, hibernate and spring.

To run an integration test using hibernate, substitute the normal datasource (e.g. org.springframework.jdbc.datasource.DriverManagerDataSource@) with an instance of @org.unitils.database.UnitilsDataSourceFactoryBean in your spring application context. This bean picks up the configuration held in a file called unitils.properties (see source:trunk/cdmlib/cdmlib-persistence/src/test/resources/unitils.properties) located somewhere in your classpath. To specify the application context to use in the test, use the annotation @SpringApplicationContext e.g.

@SpringApplicationContext("path/to/your/applicationContext.xml")
public class PersistenceTest extends UnitilsJUnit4 {

  @SpringBeanByType
    private DaoUnderTest daoUnderTest;
    private String testData;

    @Before
    public void setup() {
        testData = "Lorem";
    }

    @Test
    @DataSet("PersistenceTest.testMethod.xml")
    @ExpectedDataSet
    public void testMethod() {
        String result = daoUnderTest.method(testData);

        assertEquals("Ipsum",result);
    }  
}

Unitils will autowire properties annotated with @SpringBeanByType and @SpringBeanByName with beans from your application context. Annotating the class or individual test methods with @DataSet("datasetname.xml") results in unitils loading a dbunit dataset into the test database prior to the test starting up. If the test class is located in eu/etaxonomy/cdmlib/persistence/ then the data set needs to be located there too. @ExpectedDataSet checks the state of the database at the end of the test against a data set file located in package/ called TestClassName.testMethodName-result.xml . There is no such convention for @@DataSet@, but I follow the same convention any way, naming dataset input files @TestClassName.testMethod.xml@.

Testing service and controller layer classes using mocks

If you don't depend upon external code that is too complicated to replicate otherwise, you can avoid using spring and hibernate To fully isolate the class under test, you can inject mock objects into the dependencies of a class under test, to verify its behaviour without creating real objects which can act as a point of failure outside of the class under test. EasyMock is a framework that provides mock objects, and provides tools for verifying their behaviour.

public class ControllerTest extends UnitlsJUnit4 {
    @Mock
    @InjectInto(property = "service")
    private Service mockService

    @TestedObject
    private TestController controllerUnderTest;

    private String testData;

   @Before
    public void setup() {
        testData = "Lorem";
        mockService = EasyMock.createMock(Service.class);
        controllerUnderTest = new TestController();
    }

    @Test
    public void testMethod() {
        EasyMock.expect(mockService.serviceMethod(EasyMock.eq(testData))).andReturn("Ipsum");
        EasyMock.replay(mockService);

        String result = controllerUnderTest.method(testData);

        assertEquals("Ipsum",result);
        EasyMock.verify(mockService)
    }  
}

In the example above, there is an instance (declared using an interface) mockService which we're not interested in testing and an instance controllerUnderTest which we do want to unit test. The behaviour of TestController which we're testing depends upon an instance of Service being injected in to our controller. By annotating our mock with @Mock and @InjectInto and annotating our class under test with @@TestedObject@, unitils handles the dependency injection for us.

We still need to create our mock, using the static method @EasyMock.createMock(Interface.class)@. We then tell easymock what behaviour we expect of our mocks (in this case, we expect the method serviceMethod to be called, and we expect the argument to be equal to testData). We want our mock to return the value "Ipsum" when this method is called.

Once we've finished defining the expected behaviour of our mocks we call EasyMock.replay(mock1, mock2, . . .)@. We then run our test. After the test has finished, we call @EasyMock.verify(mock 1, mock2, . . . ) to verify that the expected behaviour occurred.

Updated by Katja Luther almost 2 years ago · 10 revisions