Unit Testing DAO Layer: A Practical Guide

When unit testing the DAO / Repository interfaces, it is very important to know what we are testing and what assumptions we are asserting.

Unit-test-dao-layer

If we are working on a project built on Spring, hibernate or JPA, and we want to unit test its data access layer (DAO) the information given in this tutorial may help us. You can also checkout the general best practices for writing excellent unit tests.

1. What to Include and Exclude in the Unit Tests

First of all, we should be very clear about our goals before writing any kind of tests in the application. Generally, unit tests help to verify the logic written by the application developers in different modules. Unit tests, in reality, do not help in testing the core features provided by underlying libraries and frameworks.

In the case of unit testing the DAO layer, testing the simple CRUD methods of a Spring-provided Repository may not add much value as they are already tested inside the framework and from a larger open-source community.

Still, based on application design and requirements, developers write custom queries, and that is where a defect can sneak into the application code. We must ensure that such queries are tested with each release to rule out any bigger issues in the system.

The following test is an example of a unit test providing no value. It saves a record in the database and then checks the record id. Until the database connectivity fails, this test will always PASS. And we are using an in-memory test database or testcontainers so connectivity will never fail.

Item item = new Item(null, "Red Chili", 3, "Spices");
Item savedItem = mongoTemplate.save(item);

Assertions.assertNotNull(savedItem.getId());

mongoTemplate.remove(savedItem);

2. Write the Correct Assumptions

When testing the DAO methods, it is very important to determine what assumptions we are asserting.

For example, a unit test may assert that a filter expression is returning 2 records which might be the correct number of records. But we must also assert that the returned records are specifically those records that we are expecting. This must be done by asserting the record ids or the value of fields on which the filter has been applied.

For example, we have the following ItemRepository interface with such filter method.

public interface ItemRepository extends MongoRepository<Item, Integer> {

  @Query("{'quantity': {$gte : ?0, $lte : ?1}}")
  List<Item> findAllByQuantityBetween(int qtyGT, int qtyLT);
}

In the unit test, we are performing the following steps:

  • Load initial data
  • Test DAO method
  • Assert the test output. It is very important to assert the right thing
  • Clean up the database for the next test
@Test
void testFindAllItemsByQuantityBetween(){
  
  //Initial Test data
  Item item1 = new Item(null, "Item 1", 1, "Category 1");
  Item item5 = new Item(null, "Item 5", 5, "Category 5");
  Item item8 = new Item(null, "Item 8", 8, "Category 8");
  Item item12 = new Item(null, "Item 12", 12, "Category 12");

  mongoTemplate.save(item1);
  mongoTemplate.save(item5);
  mongoTemplate.save(item8);
  mongoTemplate.save(item12);

  //Test
  List<Item> items = itemRepository.findAllByQuantityBetween(4, 10);

  //Assert
  Assertions.assertEquals(2, items.size());
  Assertions.assertArrayEquals(new Item[]{item5, item8}, items.toArray());

  //Remove test data
  mongoTemplate.remove(item1);
  mongoTemplate.remove(item5);
  mongoTemplate.remove(item8);
  mongoTemplate.remove(item12);
}

3. Never Use a Physical Database

As we will test the DAO layer, we will need access to a database as well. But we should not use any existing physical database for a few reasons such that it may corrupt the test data, which is primarily prepared for integration tests, or simply because some other team members need access to that data too.

Unit-test-dao-layer

To solve these issues, always an in-memory database. IM (in-memory) database is a good option because it does not leave any trace back and we are sure that we will execute the tests with the configured initial data, always.

4. Write Tests with Zero Side Effects

A good unit test should leave the database state same as it was before test case execution. It should remove all added data; and roll back all updates.

As a best practice, a test should initialize the test data before the test starts and cleanup the test data after the test ends. A test should never leave any traces behind in the database that can affect the results of other tests.

Also, the tests should not rely on a particular test execution order. If you see @Order annotation in tests, consider refactoring your code or rewriting the tests.

Feel free to drop me your queries and suggestions.

Happy Learning !!

Leave a Comment

  1. When ever i run the tests ,the run test cases are being added to my original database.It is continously adding new entries for each test run.I even used rollback annotation but how do we destroy them soon after running the testcases.I am using hibernateTemplate in Dao implementation.

    Reply
  2. hi,
    when i am running the test case i am getting NULL pointer exception but from main class its working fine .but from testcase entity manager is not initializing.

    java.lang.NullPointerException
    at com.nt.dao.UserDAOImpl.addUser(UserDAOImpl.java:57)
    	at com.nt.test.TestUserDAO.testAddUser(TestUserDAO.java:47)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    Reply
  3. Hey,

    I got a similar test case where I do test the DAO Layer.

    Considering you example:

        public void testAddDepartment()
        {
            DepartmentEntity department = new DepartmentEntity("Information Technology");
            departmentDAO.addDepartment(department);
             
            List<DepartmentEntity> departments = departmentDAO.getAllDepartments();
            Assert.assertEquals(department.getName(), departments.get(0).getName());
        }
    

    When I debug the test code (my one), set a breakpoint at the last line in (the method above), and compare the reference of

    department

    and

    departments.get(0)

    I see that they are both the same.

    So my concern or question is if the test is useful at all, as the loaded

    departments.get(0)

    is the same as

    department

    .
    I think the tests are useful but just realized that the references are the same (probably as the session/transaction) does not load it newly from the db and provides the already existent entity.

    Reply
  4. Thanks Lokesh, This was helpful. Do you think there is a setting to use different hibernate.cfg.xml when unit tests are run as compared to actual hibernate configuration file? I have a separate hibernate utility class which creates a sessionFactory. The sessions are actually created and closed in the Dao layer after accessing the database. So when I use the inmemory database for dao layer testing I want hibernate configuration to connect to a different database.

    Thanks in advance!

    Reply
  5. Hi Lokesh, Thanks for this article.

    We have Dbunit to test database logic(ie, DAO layer) in unit testing.
    My doubt is, dbunit is like a mockito or not?
    Mockito is to create mock objects for java classes and Dbunit is to create mock database (in the form of xml).

    Am I correct? Anyone help me in mockito and dbunit . I am working spring webservices and hibernate.

    Reply
  6. Hi,

    Thank you for useful tutorial.

    I am implementing it on Spring+Hibernate project. I am using HSQLDB, While implementing I am facing below problem.

    I have created some objects in before method, saved them. But inside DAO method select query(using Hibernate Query API) returns nothing, however Hibernate’s load, get, find API method returns proper data.

    Apart from this, could you please explain little bit, how does HSQL db (in memory version) work.

    Reply
    • Then there must be something wrong with SELECT query itself, perhaps. You can try printing query in log, and then analyze it by running against some table in any physical DB such as MySQL. An interesting SO thread.

      Reply
    • I do not call them logic. They are steps. In unit test, each test should be complete in itself, and should never rely on any other tests. If if needs some code to be written multiple times, so be it.

      Reply
  7. a) Disagree. There is no such rule like this.
    b) It depends from application to application. If ORM is a framework like hibernate, DAO is essentially very thin (still I would like to test entity relationships). And if ORM is like iBatis, which rely on user written SQL, then testing the code becomes very very essential.

    Reply
    • Lokesh, I suggest you to read again the definition of unit test.
      A test that is talking to a database if definitely not a unit test.

      For b) it makes sense to write a test at the DAO layer to ensure that your queries and mappings work, but you don’t want to acceptance test your DAO.

      Reply
  8. Is it necessary for an entity class to implement Serializable interface?
    I wanted to know the benefits of implementing one, apart from that the entity could be serialised for later use.

    Reply

Leave a Comment

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.