Unit Testing DAO Layer: A Practical Guide

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.

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 !!

Comments

Subscribe
Notify of
guest
17 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

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.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode