Showing posts with label TestNg. Show all posts
Showing posts with label TestNg. Show all posts

Thursday, July 12, 2012

Spring MVC + Hibernate: testing the persistence layer (DAOs) with TestNg

Whenever you're working on a Spring MVC project, you might want to unit tests each single layer of your application. In this post, we'll see how we can perform unit testing on the persistence layer, using TestNg. More specifically, we'll go through unit testing a persistence layer that is built upon a DAO pattern, implemented using Hibernate.

DAO classes
The DAO classes that we are going to test in this example are structured like this:


As general CRUD methods are implemented in the GenericDaoImpl abstract class, we'll consider that our UserDaoImpl class is empty, in order to keep things simple:

@Repository
public class UserDaoImpl extends GenericDaoImpl<user, long=""> implements UserDao {
 // methods specific to this concrete DAO
}



As you can see, this class is annotated with @Repository, which registers it as a "Repository" type Spring bean.

Spring configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 <context:component-scan base-package="be.glimmo">
 
 <!-- Enabling annotation-driven transaction management -->
 <tx:annotation-driven />

 <!-- Hibernate SessionFactory -->
 <bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="hibernateProperties">
   <props>
    <!-- Use the C3P0 connection pool provider -->
    <prop key="hibernate.c3p0.min_size">5</prop>
    <prop key="hibernate.c3p0.max_size">20</prop>
    <prop key="hibernate.c3p0.timeout">1800</prop>
    <prop key="hibernate.c3p0.max_statements">50</prop>
    <prop key="hibernate.c3p0.idle_test_period">300</prop>
    <prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop>
    <prop key="hibernate.hbm2ddl.auto">create-drop</prop>

    <!-- Show and print nice SQL on stdout -->
    <prop key="show_sql">true</prop>
    <prop key="format_sql">true</prop>
   </props>
  </property>
  <property name="packagesToScan" value="be.glimmo.domain" />
 </bean>

 <!-- Defining a transaction manager bean (used for annotation-driven transaction 
  management -->
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
  <property name="dataSource" ref="dataSource" />
 </bean>

 <!-- Defining the Datasource bean -->
 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
  <property name="url" value="jdbc:derby://localhost:1527/glimmo-testdb" />
  <property name="username" value="glimmo" />
  <property name="password" value="password" />
  <property name="minIdle" value="5" />
  <property name="maxActive" value="10" />
  <property name="defaultAutoCommit" value="false" />
 </bean>
</beans>

An important thing here: make sure you specify a "dataSource" bean. Although we would usually declare a bean with the ID "dataSource", when working with Spring, just know that it is also possible to have Spring properly configured without this kind of bean. Nevertheless, configuring unit testing with TestNg simply requires us to declare this bean. More (we'll see why, later).

TestNg test class
Now, let's take a look at an example of test class that will allow us to test each single method of our DAO:


@Test
@ContextConfiguration(locations={"classpath:Glimmo-context-test-configuration.xml", "classpath:Glimmo-persistence-test-configuration.xml"})
public class TestUserDaoDummy extends AbstractTransactionalTestNGSpringContextTests{
 
 // TestNg Data Providers
 Object[][] users = new Object[][]{
   new Object []{"user1", "user1@test.com", "user_firstname", "user_lastname", 
     UserGrade.ADMIN, "password", null}
 };
 
 @DataProvider(name="usersProvider")
 public Object[][] provideUsersForCreation(){
  return users;
 }
 
 @Autowired
 private UserDao userDao;
 
 @Test(dataProvider="usersProvider")
 @Rollback(false)
 public void testUserCreation(String username, String email, String firstName, String lastName, UserGrade userGrade, String password, Date gradeEnd) {
  User newUser = new User(username, email, firstName, lastName);
  newUser.setGrade(userGrade);
  newUser.setPassword(password);
  userDao.save(newUser);
  
  Assert.assertNotEquals(super.countRowsInTable("USERS"), 0);
 }
}



There are 2 important things to pay attention to, here:

  • Have the test class extends AbstractTransactionalTestNGSpringContextTests
The AbstractTransactionalTestNGSpringContextTests class helps you manage the transactional aspects when testing the methods from the DAO class (as a matter of fact, it sets the @Transactional annotation at class level, so that you don't need to worry about transactions when writing your test methods). It also provide convenience methods (based upon the underlying SimpleJdbcTemplate instance...) to query the DB, such as executeSqlScript(...) and countRowsInTable(...). This class expects a bean with the ID "dataSource". That's the reason why I said on the previous section that our Spring configuration has to specify such a bean.



  • Annotate your test class with @ContextConfiguration
This annotation is provided by Spring for test purposes and allows you to load Spring configuration file. This annotation supports several ways to specify which configuration to load but its "locations" attribute is likely the easiest and most used one: we just have to provide a comma separated list of String values. Each of them , referring to a Spring XML configuration file that needs to be loaded in order to initialize the Spring application context. In the example, I prefixed each reference with "classpath:" meaning that the configuration files will be retrieved from the classpath of my project (since I'm working on a Maven based project, I simply put them in the src/test/resources directory).

Now, you're able to wire the DAO classes (as long as they're registered as Spring beans) into you test class, and test every single of their methods. VoilĂ !

Friday, May 18, 2012

TestNG for starters : running a same test method several times with different parameter values

When using TestNg to write unit tests, one of the most common mistake I see people make is writing a test method in which they make several assertions in order to test giving different values as input to the method to be tested.

To illustrate this common mistake, let's assume we have a class named StringConcatener, which provides a static method to concatenate 2 given String objects :

public class StringConcatener {
 
 public static String concatStrings(String s1, String s2){
  if(s1 == null){
   s1 = "null";
  }
  
  return s1 + s2;
 }
}

Now, we are going to write a test class in which we will implement a method that will check the behavior of our concatStrings( ) method against different input values. To do this, new TestNg users often tend to stuff lots of assertions inside a same test method, just like this : 


public class TestStringConcatener {

 @Test
 public void testConcatener() {
  Assert.assertEquals(StringConcatener.concatStrings("null", "null"), "nullnull");
  Assert.assertEquals(StringConcatener.concatStrings(null, null), "nullnull");
  Assert.assertEquals(StringConcatener.concatStrings("StringA", "StringB"), "StringAStringB");
  Assert.assertEquals(StringConcatener.concatStrings("StringA", null), "StringAnull");
  // and many other assertions...
 }
}

This test method works fine but only if every assertions get the expected result. Suppose the 1st assertion does not get the expected result. Then, none of the assertions that follows it will be tested. That, obviously, is not how you want your test class to behave.

Solution


  1. Write a test method with 3 arguments : the 2 String objects to be concatenated + the expected result
  2. Marked this test method with @Test(dataProvider="EqualsAssertionsProvider") : this method is a test method which expects input from the DataProvider named "EqualsAssertionsProvider"
  3. Write the method that will provide the set of input values to the test method, and mark it with @DataProvider(name="EqualsAssertionsProvider"). The name attribute in the @DataProvider annotation specifies its name. 

public class TestStringConcatener {
 
 @DataProvider(name="EqualsAssertionsProvider")
 public Object[][] createConcatenationAssertionSet(){
  return new Object[][]{
    new Object []{"null", "null", "nullnull"},
    new Object []{null, null, "nullnull"},
    new Object []{"StringA", "StringB", "StringAStringB"},
    new Object []{"StringA", null, "StringAnull"},
  };
 }
 
 @Test(dataProvider="EqualsAssertionsProvider")
 public void testConcatener(String s1, String s2, String expectedResult){
  Assert.assertEquals(StringConcatener.concatStrings(s1, s2), expectedResult);
 }
}

As you can see, in the example above, the createConcatenationAssertionSet( ) method returns a 2 dimensional Object array :

  • the first dimension determines how many input sets the DataProvider holds (that is, how many times our test method will run)
  • the second dimension holds Object arrays that contains the values for each argument from our test method. As there are 3 arguments in our test method, each of these Object arrays contains 3 values.
Now that you're working with a DataProvider, should one of your assertions fail, the following ones will still be tested. To illustrate that, let's modify the expected result of the third input set from our DataProvider : 

new Object []{"StringA", "StringB", "StringAStringB"}

becomes

new Object []{"StringA", "StringB", "StringAStringX"}
Let's run our TestClass and see what are the results :


As you can see, the third assertions failed (the exact reason is shown on the right column), but the 4th assertions has still been checked. VoilĂ !

Look out!

As strange as it may look, DataProvider methods can only have 2 type of returns : Object[ ][ ] or Iterator<Object>[ ]. Maybe that would change in the future but at the time I wrote this post, I was using the latest available version of TestNg (6.3.1)