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

Lightweight Integration Testing

DZone's Guide to

Lightweight Integration Testing

In this code-heavy tutorial, learn how to perform light-weight integration testing by implementing a login management system.

· Performance Zone ·
Free Resource

Sensu is an open source monitoring event pipeline. Try it today.

Virtually all enterprise applications use data and require communication with databases and hence some kind of persistence layer implementation. Provided having permanently increasing requirements for application availability and flexibility, it becomes critical to have the possibility to test the persistence layer quickly and effectively.

Suppose we are to implement a login management system. According to the specifications, it should be implemented as a microservice and provide a possibility to retrieve login information from the underlying database.

The domain entity and data access object are presented in Listing 1 and Listing 2, respectively.

Listing 1: LoginInfo entity

@NamedNativeQueries({
    @NamedNativeQuery(name = LoginInfo.FIND_LATEST_LOGINS,
     query = "SELECT DISTINCT li1.deviceInfo FROM (SELECT li.deviceInfo FROM LoginInfo li WHERE li.userId = :userId ORDER BY li.loginDate DESC) li1")
})
@JsonbPropertyOrder({"deviceData", "loginTimestamp"})
@Entity
public class LoginInfo implements Serializable {

   private static final long serialVersionUID = 1L;
   public static final String FIND_LATEST_LOGINS = "findLatestLogins";

   @Id
   @SequenceGenerator(name = "LoginInfo_Generator", sequenceName = "LoginInfo_seq", allocationSize = 1)
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "LoginInfo_Generator")
   private long id;

   private String userId;

   @JsonbProperty("deviceData")
   private String deviceInfo;

   @JsonbProperty("loginTimestamp")
   private LocalDateTime loginDate;

  // getters and setters . . .

Listing 2: LoginInfoDao class

public class LoginInfoDao {
    @PersistenceContext(name = "login-info-model", unitName = "loginInfoPU")
    private EntityManager em;

    public List<String> findLatesLogins(String userId) {
        return em.createNamedQuery(LoginInfo.FIND_LATEST_LOGINS)
                 .setParameter("userId", userId)
                 .setMaxResults(5)
                 .getResultList();
}

    public void create(LoginInfo loginInfoData) {
        em.persist(loginInfoData);
    }
}

Our task is to develop a quick and comprehensive test for checking the DAO functionality. One option is to use Arquillian Persistence extension, which enriches Arquillian dynamic deployment with DBUnit support and provides a possibility to use annotations for handling test data.

Usage of Arquillian Persistence Extension for Integration Testing

First of all, we need to add necessary dependencies to our PM file (assume we use the wildfly-swarm container):

<dependency>
   <groupId>org.jboss.arquillian.junit</groupId>
   <artifactId>arquillian-junit-container</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.jboss.arquillian.extension</groupId>
   <artifactId>arquillian-persistence-dbunit</artifactId>
   <version>1.0.0.Alpha7</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.wildfly.swarm</groupId>
   <artifactId>arquillian</artifactId>
   <scope>test</scope>
</dependency>

Then, we add test persistence.xml descriptor, Arquillian descriptor, and DBUnit XML file containing test data:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="loginInfoPU" transaction-type="JTA">
    <description>LoginInfo Persistence Unit</description>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
      <property name="javax.persistence.schema-generation.scripts.action" value="drop-and-create"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
    </properties>
  </persistence-unit>
</persistence>
<arquillian xmlns="http://jboss.org/schema/arquillian"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="
        http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
     <!-- Configuration to be used when the WildFly managed profile is active -->
    <container qualifier="widlfly-managed" default="true">
        <configuration>
             <property name="javaVmArguments">-DIGDB_API_KEY=dummyKey -DIGDB_HOST=http://127.0.0.1:8071</property>
        </configuration>
    </container>
    <extension qualifier="persistence">
    <property name="defaultDataSource">java:jboss/datasources/ExampleDS</property>
    <property name="scriptsToExecuteBeforeTest">SET REFERENTIAL_INTEGRITY FALSE;</property>
    <property name="userTransactionJndi">java:jboss/UserTransaction</property>
    <property name="dumpData">true</property>
    <property name="dumpDirectory">/tmp/showcase</property>
</extension>
</arquillian>
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <LoginInfo id="1" userId="777" deviceInfo="X-User-Agent: Mozilla" loginDate="2017-12-08" />
    <LoginInfo id="2" userId="777" deviceInfo="X-User-Agent: Mozilla" loginDate="2017-12-09" />
    <LoginInfo id="3" userId="777" deviceInfo="X-User-Agent: IE" loginDate="2017-12-10" />
    <LoginInfo id="4" userId="777" deviceInfo="X-User-Agent: IE" loginDate="2017-12-11" />
    <LoginInfo id="7" userId="777" deviceInfo="X-User-Agent: Chrome" loginDate="2017-12-12" />
    <LoginInfo id="8" userId="777" deviceInfo="X-User-Agent: Chrome" loginDate="2017-12-12" />
    <LoginInfo id="9" userId="777" deviceInfo="X-User-Agent: Opera" loginDate="2017-12-13" />
    <LoginInfo id="10" userId="777" deviceInfo="X-User-Agent: Opera" loginDate="2017-12-14" />
    <LoginInfo id="11" userId="777" deviceInfo="X-User-Agent: XXX" loginDate="2017-12-19" />
    <LoginInfo id="12" userId="777" deviceInfo="X-User-Agent: XX" loginDate="2017-12-19" />
    <LoginInfo id="13" userId="777" deviceInfo="X-User-Agent: XX" loginDate="2017-12-29" />
    <LoginInfo id="14" userId="777" deviceInfo="X-User-Agent: XXXX" loginDate="2018-12-09" />
</dataset>

Now, we can create the following integration test.

Listing 3: Integration test based on Arquillian Persistence extension

import java.time.LocalDateTime;
import java.util.List;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.myfirm.loginservice.control.LoginInfoDao;

@RunWith(Arquillian.class)
public class LoginInfoArquillianPersistenceIT {

    private static final String TEST_USER_ID = "888";
    private static final String TEST_DEVICE_INFO = "X-User-Agent: Chrome";
    private static final LocalDateTime TEST_LOGIN_DATE = LocalDateTime.of(2017,  12, 12, 0, 0);

    @Deployment
    public static WebArchive createDeploymentPackage() {
        return ShrinkWrap.create(WebArchive.class, LoginInfoArquillianPersistenceIT.class.getName() + ".war")
                .addClasses(
                        LoginInfoDao.class,
                        LoginInfo.class
                 )
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); 
    }

    @Inject
    private LoginInfoDao loginDao;
    @Resource
    private javax.transaction.UserTransaction userTransaction;

    @Test
    @UsingDataSet("datasets/loginInfoTestData.xml")
    public void testFindLatestLogins() {
        List<String> loginInfoData = loginDao.findLatesLogins("777");
        Assert.assertNotNull(loginInfoData);
        Assert.assertEquals(5, loginInfoData.size());
    }

    @Test
    @ShouldMatchDataSet(value="datasets/expectedNewLoginInfo.xml", excludeColumns="id")
    public void testCreate() {
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setUserId(TEST_USER_ID);
        loginInfo.setDeviceInfo(TEST_DEVICE_INFO);
        loginInfo.setLoginDate(TEST_LOGIN_DATE);
        loginDao.create(loginInfo);
    }
}

This way, we created a deployment simulation, which allows testing our persistence layer, including JTA data source and CDI.

Sometimes, we can get even more light-weight and isolated. For example, if we need just to test our persistence stuff without any container functionality, we can use application-managed EntityManager and inject it manually. To test JPA stuff in transactional context, we can use the hibernate-testing library, which greatly simplifies transaction handling in integration tests.

Usage of Hibernate-Testing Library for Integration Testing

In this case, we don't want to use any container, so we need to do some additional work for creating a minimalistic JPA environment. So, we create an application-managed EntityManager object and "inject" it into our DAO object with the usage of java.reflection package, see Listing 4.

Listing 4: Persistence integration test with the usage of hibernate-testing library

import static org.hibernate.testing.transaction.TransactionUtil.*;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import org.dbunit.Assertion;
import org.dbunit.IDatabaseTester;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.h2.tools.RunScript;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.myfirm.loginservice.control.LoginInfoDao;

public class LoginInfoHibernateTestingPersistenceIT {

    private static final String JDBC_DRIVER = org.h2.Driver.class.getName();
    private static final String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
    private static final String USER = "sa";
    private static final String PASSWORD = "sa";
    private static final String TEST_USER_ID = "888";
    private static final String TEST_DEVICE_INFO = "X-User-Agent: Chrome";
    private static final LocalDateTime TEST_LOGIN_DATE = LocalDateTime.of(2017,  12, 12, 0, 0);

    private static final Logger logger = Factory.getLogger(LoginInfoHibernateTestingPersistenceIT.class);

    private EntityManagerFactory emf;
    private LoginInfoDao loginDao;
    private Field emField;
    private IDatabaseTester databaseTester;

    @BeforeClass
    public static void createSchema() throws Exception {
       RunScript.execute(JDBC_URL, USER, PASSWORD, "src/test/resources/datasets/createSchema.sql", null, false);
    }

    public LoginInfoHibernateTestingPersistenceIT() throws NoSuchFieldException, SecurityException, ClassNotFoundException {
        loginDao = new LoginInfoDao();
        emField = loginDao.getClass().getDeclaredField("em");
        emField.setAccessible(true);
        databaseTester = new JdbcDatabaseTester(JDBC_DRIVER, JDBC_URL, USER, PASSWORD);
    }

    @Test
    public void testFindLatesLoginsQuery() throws Exception {
        cleanlyInsert(getDataSet("src/test/resources/datasets/loginInfoTestData.xml"));
        doInJPA(this::entityManagerFactory, entityManager -> {
            List<String> loginInfoData = entityManager.createNamedQuery(LoginInfo.FIND_LATEST_LOGINS)
                                          .setParameter("userId", "777")
                                          .setMaxResults(5)
                                          .getResultList();
            Assert.assertEquals(5, loginInfoData.size());
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XXXX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XXX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: Chrome"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: Opera"));
            Assert.assertFalse(loginInfoData.contains("X-User-Agent: IE"));
            Assert.assertFalse(loginInfoData.contains("X-User-Agent: Mozilla"));
        } );
    }

    @Test
    public void testFindLatesLogins() throws Exception {
        cleanlyInsert(getDataSet("src/test/resources/datasets/loginInfoTestData.xml"));
        doInJPA(this::entityManagerFactory, entityManager -> {
            injectEntityManager(entityManager);
            List<String> loginInfoData = loginDao.findLatesLogins("777");
            Assert.assertEquals(5, loginInfoData.size());
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XXXX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XXX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: XX"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: Chrome"));
            Assert.assertTrue(loginInfoData.contains("X-User-Agent: Opera"));
            Assert.assertFalse(loginInfoData.contains("X-User-Agent: IE"));
            Assert.assertFalse(loginInfoData.contains("X-User-Agent: Mozilla"));
        } );
    }

    @Test
    public void testCreate() throws Exception {
        clean(getDataSet("src/test/resources/datasets/loginInfoTestData.xml"));
        doInJPA(this::entityManagerFactory, entityManager -> {
           injectEntityManager(entityManager);
           LoginInfo loginInfo = new LoginInfo();
           loginInfo.setUserId(TEST_USER_ID);
           loginInfo.setDeviceInfo(TEST_DEVICE_INFO);
           loginInfo.setLoginDate(TEST_LOGIN_DATE);
           loginDao.create(loginInfo);
        } );
        //Check the result
        try {
           ITable actualTable = databaseTester.getConnection()
             .createQueryTable("test_query", "SELECT * FROM LoginInfo");
           IDataSet expectedDataSet = new FlatXmlDataSetBuilder()
             .build(new File("src/test/resources/datasets/expectedNewLoginInfo.xml"));
           ITable expectedTable = expectedDataSet.getTable("LoginInfo");
           Assertion.assertEquals(expectedTable, actualTable);
        } catch (Exception e) {
        logger.error("testCreate2() - error while getting test results.", e);
        }
    }

    private EntityManagerFactory entityManagerFactory() {
        if (emf == null) {
            Map<String, String> props = new HashMap<>();
            props.put("javax.persistence.jdbc.driver", JDBC_DRIVER);
            props.put("javax.persistence.jdbc.url", JDBC_URL);
            props.put("javax.persistence.jdbc.user", USER);
            props.put("javax.persistence.jdbc.password", PASSWORD);
            emf = Persistence.createEntityManagerFactory("loginInfoPU", props);
         }
         return emf;
    }

    private void injectEntityManager(EntityManager em){
        try {
             emField.set(loginDao, em);
        } catch (IllegalArgumentException | IllegalAccessException e) {
           logger.error("Error while injecting EntityManager into DeviceSignatureDao.", e);
        }
    }

   private IDataSet getDataSet(String datasetLocation) throws Exception {
      return new FlatXmlDataSetBuilder()
        .build(new FileInputStream(datasetLocation));
   }

   private void cleanlyInsert(IDataSet dataSet) throws Exception {
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.setDataSet(dataSet);
        databaseTester.onSetup();
   }

   private void clean(IDataSet dataSet) throws Exception {
        databaseTester.setSetUpOperation(DatabaseOperation.DELETE_ALL);
        databaseTester.setDataSet(dataSet);
        databaseTester.onSetup();
   }
}

    To use the hibernate-testing library, we need to add the following Maven dependency:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-testing</artifactId>
   <version>${hibernate.version}</version>
   <scope>test</scope>
   <!-- Sometimes, we need to add this exclusion to get rid of 
   the following problem: Could not find artifact com.sun:tools:jar:1.6  -->
   <exclusions> 
      <exclusion>
         <artifactId>tools</artifactId>
         <groupId>com.sun</groupId>
      </exclusion>
   </exclusions>
</dependency>

This way, we have got a light-weight integration test, which runs very fast and can also be used for the development and debugging of JPQL/SQL queries.

Sensu: workflow automation for monitoring. Learn more—download the whitepaper.

Topics:
integration testing ,performance ,lightweight ,tutorial

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}