The art of testing an EJB application – Part II (mocking)
The previous post on testing was more like an introduction – now it’s time for some real testing. While I’ve only touched the surface with unit-unit tests, I think functional testing needs a deeper drilling. Here we go!
Service encapsulation
Decoupling services (having one service dependent on others, that encapsulate bits and pieces of functionality) is a fairly good practices. This ties nicely with a dependency injection feature of an EJB container; beans get resolved, initiated – happy days. Surely, not only EJB container does it, but comparing CDI frameworks is not in scope of this post. It’s about focused integration testing so let me start with with a simple example – a business service utilizing the TrimService from the previous post. If we are trying to keep our tests quick and robust, we need to get into container’s shoes and perform dependency injection on our own – this is where the loosely coupling starts to be a little bit painful.
@Stateless
public class BusinessService implements BusinessServiceLocal {
@EJB TrimServiceLocal trimService;
public BusinessMethodResponse businessMethod(TransferObject obj) {
//some logic
}
}
In this particular example the container does a fairly simple and straightforward job. Instantiating of this service is nothing extraordinary and there is nothing that should stop us doing the same during in tests.
public class BusinessServiceTest {
BusinessService businessService;
@BeforeClass
public void setup() {
businessService = new BusinessService();
businessService.trimService = new TrimService();
}
@Test
public void successfulBusinessMethodForSpecificCase() {
TransferObject obj = new TransferObject();
//populate the object
BusinessMethodResponse response = businessService.businessMethod(obj);
Assert.assertNotNull(response);
}
}
Please note that the trimService field does not have an explicit visibility defined, which make the field of package scope. If we keep tests in the same package as the service, we can fiddle with service’s fields without any reflection API magic.
Even though this can work quite smoothly, we are moving beyond unit testing (some might say some unit tests principles might be violated, because we check the behaviour of more than one class – TrimService is tested as well). I won’t be bothered about this; at some point it’s necessary to take a more holistic approach towards testing. That’s exactly what’s happening here: focused integration testing; a carefully curated set of features was chosen and tested in isolation. That way we can see how certain blocks of the application works in conjunction with other elements of the system. On the other hand, the tests are still kept relatively small and fast.
Mocking services
When the services are getting more and more complex, a simple initialization might appear troublesome, producing too much boilerplate code. There are ways to do it faster – with mockups. From two most popular mocking frameworks: EasyMock (http://easymock.org/) and Mockito (http://mockito.org/), I pick the latter. A quick glimpse over Mockito website should give a decent clue how it works and how it follows given-when-then principle.
See an example – a configuration service which returns configuration codes from a database. A Hibernate named query is used to retrieve raw, persisted data and some additional stripping logic is applied. Given a following service,
@Stateless
public class ConfigService implements ConfigServiceLocal {
@EJB
CrudServiceLocal crudService;
public List<String> getConfigCodes(String type) {
List<FuncConfg> funcConfgResults =
crudService.findByNamedQuery("FuncConfg.findByType", "type", type);
List<String> configCodes = //some additional processing
return configCodes;
}
}
the process of mocking data and testing may look like this
public class ConfigServiceTest {
ConfigService configService;
@BeforeClass
public void setup() {
configService = new ConfigService();
configService.crudService = Mockito.mock(CommonCrudService.class);
}
@Test
public void defaultTypeConfig() {
final String deafultType = "defaultType";
Mockito.when(configService.crudService
.findByNamedQuery("FuncConfg.findByType", "type", defaultType))
.thenReturn(defaultFuncConfgList());
List<String> confgCd = configService.getConfigCodes(defaultType);
Assert.assertNotNull(confgCd);
Assert.assertEquals(confgCd.size(), 10);
//some additional assertions
}
private List<FuncConfg> defaultFuncConfgList() {
//creates default list of codes
}
}
Handling database
Last, but not least – ‘talking’ to a database. This is getting us closer to an end-to-end integration testing – though the tests are still fairly robust, small and fast. In fact, what is happening here is pretty much the same what OpenEJB does when it bootstraps a database, but faster.
I use this approach in two scenarios: when I want to test named queries against real data and when a snapshot of a production / test database is available and can be loaded from file, instead of handcrafting and mocking each data object individually.
Let’s use the previous example of the configuration service. Mocking the crud service worked perfectly fine, but when the number of different test cases increases significantly, quite a lot of test code needs to be written. There are smarter ways to do it. In my projects I tend to use this DataSourceMockUp class which bootstraps hibernate and exposes the EntityManager.
public class DataSourceMockUp {
private EntityManager entityManager;
public DataSourceMockUp(Builder builder) {
//bootstrap hibernate
Ejb3Configuration config = new Ejb3Configuration();
config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect")
.setProperty("javax.persistence.transactionType", "RESOURCE_LOCAL")
.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver")
.setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:testdb")
.setProperty("hibernate.connection.username", "sa")
.setProperty("hibernate.connection.password", "")
.setProperty("hibernate.connection.pool_size", "1")
.setProperty("hibernate.connection.autocommit", "true")
.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider")
.setProperty("hibernate.hbm2ddl.auto", "create")
.setProperty("hibernate.show_sql", "true");
//add entities
for (Class<?> clazz : builder.annotatedClass) {
config.addAnnotatedClass(clazz);
}
//set up services
//even though the method is deprecated - this is one to be used
//https://forum.hibernate.org/viewtopic.php?f=10&t=966871
EntityManagerFactory entityManagerFactory = config.createEntityManagerFactory();
setEntityManager(entityManagerFactory.createEntityManager());
//import initial data
EntityTransaction tx = getEntityManager().getTransaction();
tx.begin();
SchemaExport schemaExport = new SchemaExport(config.getHibernateConfiguration());
schemaExport.setImportFile(builder.importSqlFile);
schemaExport.create(true, true);
tx.commit();
}
public static class Builder {
private String importSqlFile;
private List<Class<?>> annotatedClass = new ArrayList<Class<?>>();
public Builder fromSqlFile(String importSqlFile) {
this.importSqlFile = importSqlFile;
return this;
}
public Builder withAnnotatedClass(Class<?> clazz) {
this.annotatedClass.add(clazz);
return this;
}
public DataSourceMockUp build() {
return new DataSourceMockUp(this);
}
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
}
With a data source mocked, the test class looks like this
public class ConfigServiceTest {
ConfigService configService;
@BeforeClass
public void setup() {
DataSourceMockUp mockup = new DataSourceMockUp.Builder()
.fromSqlFile(sqlLocation)
.withAnnotatedClass(FuncConfg.class).build();
CommonCrudService crudService = new CommonCrudService();
crudService.entityManager = mockup.getEntityManager();
configService = new ConfigService();
configService.crudService = crudService;
}
@Test
public void getConfigCodesForDefaultType() {
List<String> codes = configService.getConfigCodes("T");
Assert.assertNotNull(codes);
//... and so on
}
}
One can argue that the same can be achieved with an OpenEJB. True, the whole container can be bootstrapped and all the services will be injected automatically (including the persistence context). However, as I’ve outlined in the first part of this series – the tests are categorised by the speed of execution and handcrafting hibernate with w pre-prepared SQL script is much faster that running the whole EJB container. What is more – it’s more hands on, errors are easier to spot and there is less magic going under the bonnet. As I am planning to show in the next post – things like error recovery, detailed configuration and so on are not 1, 2, 3 with OpenEJB. That’s why I find focused integration testing a compromise between simple unit testing and a holistic, end-to-end approach.