Overcoming Application Server Dependency
In the example, the ticket booking component is implemented as a session EJB. In a production environment, EJB runs in container, which means it has container dependency. The production environment is usually very different from the development environment. If you were to test EJB in an environment closer to the real one, you would have to build and deploy it in an application server and then test. This usually takes a time, especially when you are working with large and complex enterprise applications. If you have to build, deploy, and wait for the server to come up to see your result, it breaks your flow. This is why I like OpenEJB and it's embedded mode feature. OpenEJB simulates the container for you and helps your test run closer to the real environment. This helps avoid the tedious build and deploy cycle as OpenEJB runs within your IDE and starts or stops while running the test. I especially like the inclusion or exclusion filter which helps avoid loading of unwanted EJBs for a scenario. For this example, I kept the ticket booking session bean simple with only two methods -- one method to book a ticket and another method to check for the seat availability, as shown in the EJB local interface below:
Overcoming Data Dependency
A unit test scenario often requires a set of pre-existing data before it can run successfully. Remember the spaceship can only transport five people. The seat availability method of the session bean should return True if five seats are not filled, and False if all of them are filled. To test if it correctly returns False when all seats are booked, you should have five seats filled already before running the test. So five pre-existing ticket bookings is a prerequisite for a test.
There are also cases where the same data cannot be recorded more than once as it will be functionally incorrect as per the requirement. In our example scenario, we have a requirement that the same passenger should not be allowed to book ticket on the same date more than once; otherwise it should display an error message. To test whether this validation logic works correctly and displays error message, the database should already have an existing booking for the passenger that the test is trying to book. On the other hand, to successfully book a ticket the database should not have the same data already. In this case, we need to remove the existing data everytime before running the test. Otherwise this test cannot be repeated as running it first time itself would have created the data and thereby causing the subsequent runs to fail.
This is where DBUnit comes in handy. It helps to prepare the database to a known state so that tests can repeatedly run successfully. It has the ability to export data from database to XML and import data to the database from XML. To export data we should first have the data in the database. This seed data creation can be accomplished in multiple ways including manually inserting data using SQL. The way I created it is using the application code itself. I had the unit test case for booking the ticket ready. I simply used it to create data and visually checked for the correctness (a one-time job).
Once the required seed data has been created in the database, it needs to be exported to an XML file so that unit tests can use it. I wrote a class called DataProcessor that contains the logic for data export/import using DBUnit. This is injected into a helper class called DataHelper that can export or import data in bulk using the processors. This helper class is used by the unit test classes for preparing the data for running tests. The data processor configuration for spaceship example is shown below. Since we only need to export ticket table's data, only one query is configured.
Any number of data processors can be configured like the one above to export data into multiple XMLs for different use cases. Each processor can hold one or more queries to export data depending on the number of tables.
Once the data processor is configured, it is injected into data helper as shown below. As you can see, it can take a list of processors as parameters, allowing multiple processors for mass export or import.