Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Effective Testing around Hibernate/ORM layer

DZone's Guide to

Effective Testing around Hibernate/ORM layer

· DevOps Zone
Free Resource

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

As applications that use Hibernate or any ORM frameworks grow complex, lots of performance issues don't get enough attention until it is too late. This article accounts for SLA time as part of the junits and allows developers to think in terms of calculating the number of calls hibernate is going to make in the background. This allows for developers to deal with facts rather than assumptions. 

Assuming we are doing TDD, let's build up the TestCase first.
We are going to build up a typical API to create Person with bunch of addresses.
To understand how this works, we will create a buggy Service and include background Audit mechanism, to simulate abstraction to developer.

 
@RunWith(SpringJUnit4ClassRunner.class)
public class PersonServiceTest {
  @Autowired
  private PersonService personService;

  @Before
  public void insertData() {
    TestUtil.createTestData(personService);
  }

  @Before
  public void cleanup() {
    QueryCountHolder.clear();
  }

  @Test(timeout = 500)
  public void testAddressCount() {
    assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(0));
    assertThat(personService.getAddressCount("John"), is(2));
    assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(1));
  }

  @Test(timeout = 500)
  public void testUpdatePerson() throws Exception {

    assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(0));

    Person person = personService.getCustomerByName("John");
    assertThat(person.getGender(), CoreMatchers.nullValue());
    person.setGender("Male");

    QueryCountHolder.clear();
    personService.save(person);
    assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(3));

    person = personService.getCustomerByName("John");
    assertThat(person.getGender(), CoreMatchers.is("Male"));
  }
}

testAddressCount - Based on the service interface, this test case assume 1 sql is executed to get the count of addresses give a first name 
testUpdatePerson - We update the person gender, and assume 3 sqls are executed.
  1. To read the person
  2. Update gender
  3. Audit event to record gender change
Both the test fails 
testAddressCount java.lang.AssertionError:  Expected: is <1> got: <2>
testUpdatePerson java.lang.AssertionError:  Expected: is <3> got: <4>

Let's dive deep into the service and see what is wrong.  
@Service
public class PersonServiceImpl implements PersonService {
  @Autowired
  private PersonRepository repository;

  @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
  public int getAddressCount(String firstName) {
    List<Person> list = repository.findByFirstNameLike(firstName);
    if (list == null) {
      return 0;
    }
    Person byFirstNameLike = list.get(0);
    return byFirstNameLike == null ? 0 : byFirstNameLike.getAddresses().size();
  }

  @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
  public Person save(Person person) {
    // un intended operation
    person.setLastName(person.getLastName() + " 1");
    return repository.save(person);
  }
}


As you can see in getAddressCount, instead of writing a custom hql, the developer did an mistake of reading object and doing a size on the list. The correct way of writing this method is to make use of custom query which does a count(*) on address table. 

Similarly with Save, the test expected to update Gender, but the service was doing something more than expected, triggering a unexpected audit record on last name. 

To demonstrate this article I have made use of datasource-proxy which is very good API to capture sqls that hibernate or any other ORM frameworks execute under the hood. 

Also, I feel like it is very good practice to use @Test(timeout) so we know if some one changes a method later on, and hinder the SLA.

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}