DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Maven Plugin Testing - in a Modern Way - Part I
  • Part 3: How to Develop a Data Integration Master Test Plan
  • Accelerating Debugging in Integration Testing: An Efficient Search-Based Workflow for Impact Localization
  • Building Intelligent Integration Tests for Microservices

Trending

  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • AWS to Azure Migration: A Cloudy Journey of Challenges and Triumphs
  • How to Ensure Cross-Time Zone Data Integrity and Consistency in Global Data Pipelines
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Lightweight Integration Testing

Lightweight Integration Testing

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

By 
Sergiy Pylypets user avatar
Sergiy Pylypets
DZone Core CORE ·
Updated Apr. 30, 18 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.3K Views

Join the DZone community and get the full member experience.

Join For Free

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.

integration test Integration testing

Opinions expressed by DZone contributors are their own.

Related

  • Maven Plugin Testing - in a Modern Way - Part I
  • Part 3: How to Develop a Data Integration Master Test Plan
  • Accelerating Debugging in Integration Testing: An Efficient Search-Based Workflow for Impact Localization
  • Building Intelligent Integration Tests for Microservices

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: