Data Access Objects (DAO) are important components of any software system. Ideally the DAO classes shouldn't contain any logic other than few Data access logic. In that context, the DAO classes are less error prone while changing code. But there are several reasons, as described below, those forces us to write Unit test for DAO classes.
- In practice any code can breaks regardless of complex or simple
- Achieve 100% code coverage by unit testing
- Testing of data access logic whatsoever is written
Let me share one bitter experience from one of my project that motivated me to write unit testing for DAO classes in my next projects. A senior developer had put a seemingly harmless debugging code in a DAO method at mid night, day before the first working presentation to the Board of Directors, that prints the retrieved record's id from the database. The method was used by a business logic component that generates the next account number in the system and start over if the system has no existing accounts. The code definitely was tested by the developer before he delivered it to the version control but not by any Unit Test code rather manual functional test. He had not covered the scenario with freshly installed database because it takes a lot of hassles that includes execution of both 'remove and create' schema scripts i.e he had to access the database in some way moving away from the IDE. Anyway, the convinced developer left the office at mid night unknowingly injecting a disastrous statement that almost caused the project to be closed in the next day.
Our happy Engineering Manager had clicked the first link of application next day to create an account in the freshly installed system (we intentionally had kept the system fresh to show them how it will start from the very beginning when it would be deployed first) that caused the system to crash, with a NullPointerException trace printed on the browser, in front of the Board members who had come to see the already delayed project's first working presentation. Do I need to detail the situation in the demonstration room after that incident? I don't think so.
To consider unit testing for DAO classes we have to take the implementation technique based on the underlying technology used there. If hard code JDBC is used then Mock can be applied to test the behavior whether the Connection, Statement, ResultSet etc are called and closed properly. But if Hibernate is used to DAO , as O-R mapping for example, then it doesn't make sense to use Mock as the O-R mapper (e.g Hibernate) takes care of those raw objects. In that case, the testing goal would be:
- Test the O-R mapping and relationship configurations (e.g hbm.xml files)
- Query wrote in the method (e.g using Criteria or HQL)
And the above can't be tested using mock (to achieve the isolation for unit testing) rather use of in-memory database as Test Double (ref. Martin Fowler's article). Lets see a simple implementation of Fake Test Double technique using HSQLDB's in-memory database feature.
The following class provides the Hibernate Session to the test code:
public class HibernateUtil {
private static SessionFactory factory;
public static void setSessionFactory(SessionFactory factory) {
HibernateUtil.factory = factory;
}
}The following class creates the test schema and also helps initialize and reset the schema:
public class TestSchema {
private static final Configuration config = new Configuration();
static {
config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect").
setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver").
setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:test").
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-drop").
setProperty("hibernate.show_sql", "true").
addClass(Customer.class).
addClass(Account.class);
}
public static void reset() throws SchemaException {
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
try {
session.createQuery("Delete Customer").executeUpdate();
session.createQuery("Delete Account").executeUpdate();
} catch (HibernateException e) {
tx.rollback();
throw new SchemaException(e.getMessage());
}
finally {
tx.commit();
session.close();
}
}
public static void initialize() {
HibernateUtil.setSessionFactory(config.buildSessionFactory());
}
The actual Unite test class:
public class HibernateAccountDaoTest extends TestCase {
CustomerDao custDao;
AccountDao accountDao
public static void main(String[] args) {
junit.swingui.TestRunner.run(HibernateDaoTest.class);
}
public void setUp() {
TestSchema.initialize();
custDao = new HibernateCustomerDao();
custDao.setSession(HibernateUtil.getSession());
accountDao = new HibernateAccountDa0();
accountDao.setSession(HibernateUtil.getSession());
}
public void tearDown() {
try {
TestSchema.reset();
} catch (SchemaException e) {
e.printStackTrace();
}
}
public void testInsertAccount() {
// prepare data set
Customer customer = new Customer(1, "Name", "Address");
Account account = new Account(customer);
accountDao.insertAccount(account);
Account queriedAccount = dao.findAccountById(1);
assertNotNull(account.getBalance()));
}
}
Resources: