In this article I’m going to talk about DBUnit Rules, a small open source project I maintain which aims to simplify database testing[1].

1. Introduction

DBUnit Rules integrates JUnit and DBUnit through JUnit rules and, in case of CDI based tests, a CDI interceptor. This powerful combination lets you easily prepare the database state for testing through xml, json, xls or yaml files.

Most inspiration of DBUnit Rules was taken from Arquillian extension persistence a library for database in-container integration tests.

Source code for the upcoming examples can be found at github here: https://github.com/rmpestano/dbunit-rules-sample

2. Setup DBUnit Rules

First thing to do is to add DBunit Rules core module to your test classpath:

		<dependency>
			<groupId>com.github.dbunit-rules</groupId>
			<artifactId>core</artifactId>
			<version>0.14.0</version>
			<scope>test</scope>
		</dependency>

Secondly we need a database, for testing I recommend HSQLDB which is a very fast in-memory database, here is its maven dependency:

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.3.4</version>
			<scope>test</scope>
		</dependency>

Later A JPA provider will be needed, in this case Hibernate will be used:

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.2.20.Final</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.2.20.Final</version>
			<scope>test</scope>
		</dependency>

And the entity manager persistence.xml:

src/test/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="rulesDB" transaction-type="RESOURCE_LOCAL">

            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <class>com.github.dbunit.rules.sample.User</class>

            <properties>
                <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
                <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver" />
                <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:test;DB_CLOSE_DELAY=-1" />
                <property name="javax.persistence.jdbc.user" value="sa" />
                <property name="javax.persistence.jdbc.password" value="" />
                <property name="hibernate.hbm2ddl.auto" value="create-drop" /> (1)
                <property name="hibernate.show_sql" value="true" />
            </properties>

    </persistence-unit>

</persistence>
1 We’re creating the database from our JPA entities, but we could use a database migration tool like flyway to do this work, see example here.

and finally the JPA entity which our tests will work on:

1
2
3
4
5
6
7
8
@Entity
public class User {

    @Id
    @GeneratedValue
    private long id;

    private String name;

Now we are ready to rule our database tests!

3. Example

Create a yaml file which will be used to prepare database (with two users) before the test:

src/test/resources/dataset/users.yml
user: (1)
  - id: 1 (2)
    name: "@realpestano"
  - id: 2
    name: "@dbunit"
1 Table name is followed by :, we can have multiple tables in the same file.
2 Table rows are separated by -.
Be careful with spaces, wrong identation can lead to invalid dataset (principally in yaml datasets).
For more dataset examples, look here.

And the JUnit test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(JUnit4.class)
public class DBUnitRulesCoreTest {

    @Rule
    public EntityManagerProvider emProvider = EntityManagerProvider.instance("rulesDB");  (1)

    @Rule
    public DBUnitRule dbUnitRule = DBUnitRule.instance(emProvider.connection()); (2)


    @Test
    @DataSet("users.yml") (3)
    public void shouldListUsers() {
        List<User> users = em(). (4)
                createQuery("select u from User u").
                getResultList();
        assertThat(users).
                isNotNull().
                isNotEmpty().
                hasSize(2);
    }
}
1 EntityManagerProvider is a JUnit rule that initializes a JPA entity manager before each test class. rulesDB is the name of persistence unit;
2 DBUnit rule reads @DataSet annotations and initializes database before each test method. This rule only needs a JDBC connection to be created.
3 The dataSet configuration itself, see here for all available configuration options. Note that you can provide a comma separated list of datasets names here.
4 em() is a shortcut (import static com.github.dbunit.rules.util.EntityManagerProvider.em;) for the EntityManager that was initialized by EntityManagerProvider rule.
There is a lot of example tests here.

4. Transactions

EntityManagerProvider rule provides entity manager transactions so you can insert/delete entities in your tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    @Test
    @DataSet(value="users.yml", disableConstraints=true)
    public void shouldUpdateUser() {
        User user = (User) em().
                createQuery("select u from User u  where u.id = 1").
                getSingleResult();
        assertThat(user).isNotNull();
        assertThat(user.getName()).isEqualTo("@realpestano");
        tx().begin(); (1)
        user.setName("@rmpestano");
        em().merge(user);
        tx().commit();
        assertThat(user.getName()).isEqualTo("@rmpestano");
    }

    @Test
    @DataSet("users.yml")
    public void shouldDeleteUser() {
        User user = (User) em().
                createQuery("select u from User u  where u.id = 1").
                getSingleResult();
        assertThat(user).isNotNull();
        assertThat(user.getName()).isEqualTo("@realpestano");
        tx().begin();
        em().remove(user);
        tx().commit();
        List<User> users = em().
                createQuery("select u from User u ").
                getResultList();
        assertThat(users).
                hasSize(1);
    }
1 tx() is a shortcut for the entity manager transaction provided by EntityManagerProvider.

5. Database assertion with ExpectedDataSet

Consider the following datasets:

src/test/resources/dataset/users.yml
user: (1)
  - id: 1 (2)
    name: "@realpestano"
  - id: 2
    name: "@dbunit"

and expected dataset:

src/test/resources/dataset/expectedUser.yml
user:
  - id: 2
    name: "@dbunit"

And the following test:

1
2
3
4
5
6
7
8
9
10
11
12
    @Test
    @DataSet("users.yml")
    @ExpectedDataSet(value = "expectedUser.yml",ignoreCols = "id") (1)
    public void shouldAssertDatabaseUsingExpectedDataSet() {
        User user = (User) em().
                createQuery("select u from User u  where u.id = 1").
                getSingleResult();
        assertThat(user).isNotNull();
        tx().begin();
        em().remove(user);
        tx().commit();
    }
1 Database state after test will be compared with dataset provided by @ExpectedDataSet.

If database state is not equal then an assertion error is thrown, example imagine in test above we’ve deleted user with id=2, error would be:

junit.framework.ComparisonFailure: value (table=USER, row=0, col=name)
Expected :@dbunit
Actual   :@realpestano
 <Click to see difference>
	at org.dbunit.assertion.JUnitFailureFactory.createFailure(JUnitFailureFactory.java:39)
	at org.dbunit.assertion.DefaultFailureHandler.createFailure(DefaultFailureHandler.java:97)
	at org.dbunit.assertion.DefaultFailureHandler.handle(DefaultFailureHandler.java:223)
	at com.github.dbunit.rules.assertion.DataSetAssert.compareData(DataSetAssert.java:94)
Since version 0.9.0 (To be released at the time of writing) transactions will be able to be managed automatically at test level (useful for expected datasets cause you don’t assert db changes inside the test), see example here.

5.1. Regular expressions

Expected datasets also alow regexp in datasets:

src/test/resources/dataset/expectedUsersRegex.yml
user:
  - id: "regex:\\d+"
    name: regex:^expected user.* #expected user1
  - id: "regex:\\d+"
    name: regex:.*user2$ #expected user2
1
2
3
4
5
6
7
8
9
10
11
12
13
    @Test
    @DataSet(cleanBefore = true) (1)
    @ExpectedDataSet("expectedUsersRegex.yml")
    public void shouldAssertDatabaseUsingRegex() {
        User u = new User();
        u.setName("expected user1");
        User u2 = new User();
        u2.setName("expected user2");
        tx().begin();
        em().persist(u);
        em().persist(u2);
        tx().commit();
    }
1 You don’t need to initialize a dataset but can use cleanBefore to clear database before testing.
When you use a dataset like users.yml in @DataSet dbunit will use CLEAN_INSERT seeding strategy (by default) for all declared tables in dataset. This is why we didn’t needed cleanBefore in any other example tests.

6. Scriptable datasets

DBUnit Rules enables scripting in dataset for languages that implement JSR 233 - Scripting for the Java Platform, see this article for more information.

For this example we will introduce another JPA entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
public class Tweet {

    @Id
    @GeneratedValue
    private Long id;

    @Size(min = 1, max = 140)
    private String content;

    private Integer likes;

    @Temporal(TemporalType.DATE)
    private Date date;

    @ManyToOne(fetch = FetchType.LAZY)
    User user;

6.1. Javascript scriptable dataset

Following is a dataset which uses Javascript:

src/test/resources/datasets/dataset-with-javascript.yml
tweet:
  - id: 1
    content: "dbunit rules!"
    likes: "js:(5+5)*10/2" (1)
    user_id: 1
1 js: prefix enables javascript in datasets.

and the junit test:

1
2
3
4
5
6
7
8
9
    @Test
    @DataSet(value = "dataset-with-javascript.yml",
            cleanBefore = true,  (1)
            disableConstraints = true)  (2)
    public void shouldSeedDatabaseUsingJavaScriptInDataset() {
        Tweet tweet = (Tweet) emProvider.em().createQuery("select t from Tweet t where t.id = 1").getSingleResult();
        assertThat(tweet).isNotNull();
        assertThat(tweet.getLikes()).isEqualTo(50);
    }
1 As we don’t declared User table in dataset it will not be cleared by CLEAN_INSERT seeding strategy so we need cleanBefore to avoid conflict with other tests that insert users.
2 Disabling constraints is necessary because Tweet table depends on User.

if we do not disable constraints we will receive the error below on dataset creation:

Caused by: org.dbunit.DatabaseUnitException: Exception processing table name='TWEET'
	at org.dbunit.operation.AbstractBatchOperation.execute(AbstractBatchOperation.java:232)
	at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79)
	at com.github.dbunit.rules.dataset.DataSetExecutorImpl.createDataSet(DataSetExecutorImpl.java:127)
	... 21 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no parent; FK_OH8MF7R69JSK6IISPTIAOCC6L table: TWEET
	at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
If we declare User table in dataset-with-javascript.yml dataset we can remove cleanBefore and disableConstraints attributes.

6.2. Groovy scriptable dataset

Javascript comes by default in JDK but you can use other script languages like Groovy, to do so you need to add it to test classpath:

pom.xml
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-all</artifactId>
			<version>2.4.6</version>
			<scope>test</scope>
		</dependency>

If Groovy is not present in classpath we’ll receive a warn message (maybe we should fail, what do you think?):

WARNING: Could not find script engine with name groovy in classpath

Here’s our Groovy based dataset:

src/test/resources/datasets/dataset-with-groovy.yml
tweet:
  - id: "1"
    content: "dbunit rules!"
    date: "groovy:new Date()" (1)
    user_id: 1
1 groovy: prefix enables javascript in datasets.

And here is the test:

1
2
3
4
5
6
7
8
9
10
11
    @Test
    @DataSet(value = "dataset-with-groovy.yml",
            cleanBefore = true,
            disableConstraints = true)
    public void shouldSeedDatabaseUsingGroovyInDataset() throws ParseException {
        Tweet tweet = (Tweet) emProvider.em().createQuery("select t from Tweet t where t.id = '1'").getSingleResult();
        assertThat(tweet).isNotNull();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//remove time
        Date now = sdf.parse(sdf.format(new Date()));
        assertThat(tweet.getDate()).isEqualTo(now);
    }

7. Multiple databases

Multiple databases can be tested by using multiple DBUnit rule and Entity manager providers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.github.dbunit.rules.sample;

import com.github.dbunit.rules.DBUnitRule;
import com.github.dbunit.rules.api.dataset.DataSet;
import com.github.dbunit.rules.api.dataset.DataSetExecutor;
import com.github.dbunit.rules.configuration.DataSetConfig;
import com.github.dbunit.rules.connection.ConnectionHolderImpl;
import com.github.dbunit.rules.dataset.DataSetExecutorImpl;
import com.github.dbunit.rules.util.EntityManagerProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Created by pestano on 23/07/15.
 */

@RunWith(JUnit4.class)
public class MultipleDataBasesTest {

    @Rule
    public EntityManagerProvider emProvider = EntityManagerProvider.instance("pu1");

    @Rule
    public EntityManagerProvider emProvider2 = EntityManagerProvider.instance("pu2");

    @Rule
    public DBUnitRule rule1 = DBUnitRule.instance("rule1",emProvider.connection()); (1)

    @Rule
    public DBUnitRule rule2 = DBUnitRule.instance("rule2",emProvider2.connection());


    @Test
    @DataSet(value = "users.yml", executorId = "rule1") (2)
    public void shouldSeedDatabaseUsingPu1() {
        User user = (User) emProvider.em().
                createQuery("select u from User u where u.id = 1").getSingleResult();
        assertThat(user).isNotNull();
        assertThat(user.getId()).isEqualTo(1);
    }

    @Test
    @DataSet(value = "users.yml", executorId = "rule2")
    public void shouldSeedDatabaseUsingPu2() {
        User user = (User) emProvider2.em().
                createQuery("select u from User u where u.id = 1").getSingleResult();
        assertThat(user).isNotNull();
        assertThat(user.getId()).isEqualTo(1);
    }

    @Test (3)
    public void shouldSeedDatabaseUsingMultiplePus() {
        DataSetExecutor exec1 = DataSetExecutorImpl.
                instance("exec1", new ConnectionHolderImpl(emProvider.connection()));
        DataSetExecutor exec2 = DataSetExecutorImpl.
                instance("exec2", new ConnectionHolderImpl(emProvider2.connection()));

        //programmatic seed db1
        exec1.createDataSet(new DataSetConfig("users.yml"));

        exec2.createDataSet(new DataSetConfig("dataset-with-javascript.yml"));//seed db2

        //user comes from database represented by pu1
        User user = (User) emProvider.em().
                createQuery("select u from User u where u.id = 1").getSingleResult();
        assertThat(user).isNotNull();
        assertThat(user.getId()).isEqualTo(1);

        //tweets comes from pu2
        Tweet tweet = (Tweet) emProvider.em().createQuery("select t from Tweet t where t.id = 1").getSingleResult();
        assertThat(tweet).isNotNull();
        assertThat(tweet.getLikes()).isEqualTo(50);
    }

}
1 rule1 is the id of DataSetExecutor, the component responsible for database initialization in DBUnit Rules.
2 here we match dataset executor id in @DataSet annotation so in this test we are going to use database from pu1.
3 For multiple databases in same test we need to initialize database state programmatically.

8. Ruling database in CDI tests

For CDI based tests we are going to use DeltaSpike test control module and DBUnit rules CDI.

The first enables CDI in JUnit tests and the second enables DBUnit though a CDI interceptor.

8.1. Classpath dependencies

First we need DBUnit CDI: .pom.xml

		<dependency>
			<groupId>com.github.dbunit-rules</groupId>
			<artifactId>cdi</artifactId>
			<version>0.14.0</version>
			<scope>test</scope>
		</dependency>

And also DeltaSpike control module:

		<dependency> <!--1 -->
			<groupId>org.apache.deltaspike.core</groupId>
			<artifactId>deltaspike-core-impl</artifactId>
			<version>${ds.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-data-module-impl</artifactId>
			<version>${ds.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-data-module-api</artifactId>
			<version>${ds.version}</version>
		</dependency>

		<dependency> <!--2 -->
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-test-control-module-api</artifactId>
			<version>${ds.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency> <!--2 -->
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-test-control-module-impl</artifactId>
			<version>${ds.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency> <!--3 -->
			<groupId>org.apache.deltaspike.cdictrl</groupId>
			<artifactId>deltaspike-cdictrl-owb</artifactId>
			<version>${ds.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency> <!--4 -->
			<groupId>org.apache.openwebbeans</groupId>
			<artifactId>openwebbeans-impl</artifactId>
			<version>1.6.2</version>
			<scope>test</scope>
		</dependency>
1 DeltaSpike core module is base of all DeltaSpike modules
2 Test control module api and impl
3 CDI control OWB dependency, it is responsible for bootstraping CDI container
4 OpenWebBeans as CDI implementation

8.2. Configuration

For configuration we will need a beans.xml which enables DBUnit CDI interceptor:

/src/test/resources/META-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

       <interceptors>
              <class>com.github.dbunit.rules.cdi.DBUnitInterceptorImpl</class>
       </interceptors>
</beans>

And apache-deltaspike.properties to set our tests as CDI beans:

/src/test/resources/META-INF/apache-deltaspike.properties
deltaspike.testcontrol.use_test_class_as_cdi_bean=true

The test itself must be a CDI bean so DBUnit Rules can intercept it.

The last configuration needed is to produce a EntityManager for tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.github.dbunit.rules.sample.cdi;


import com.github.dbunit.rules.util.EntityManagerProvider;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;

/**
 * Created by pestano on 09/10/15.
 */
@ApplicationScoped
public class EntityManagerProducer {

    private EntityManager em;


    @Produces
    public EntityManager produce() {
        return EntityManagerProvider.instance("rulesDB").em();
    }

}

This entityManager will be used as a bridge to JDBC connection needed by DBUnit Rules.

8.3. Example

Here is a test example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(CdiTestRunner.class) (1)
@DBUnitInterceptor (2)
public class DBUnitRulesCDITest {

    @Inject
    EntityManager em; (3)



    @Test
    @DataSet("users.yml") (4)
    public void shouldListUsers() {
        List<User> users = em.
                createQuery("select u from User u").
                getResultList();
        assertThat(users).
                isNotNull().
                isNotEmpty().
                hasSize(2);
    }
}
1 DeltaSpike JUnit runner that enables CDI in tests;
2 Activates DBUnitInterceptor which will read @DataSet annotation in order to seed database before test;
3 The EntityManager we produced in previous steps;
4 This annotation enables DBUnit CDI interceptor which will prepare database state before the test execution.

All other features presented earlier, except multiple databases, are supported by DBUnit CDI.

For more examples, look at CDI module tests here.

Here is ExpectedDataSet example:

src/test/resources/datasets/expectedUsers.yml
user:
  - id: 1
    name: "expected user1"
  - id: 2
    name: "expected user2"

And the test:

1
2
3
4
5
6
7
8
9
10
11
12
13
    @Test
    @DataSet(cleanBefore = true) //needed to activate interceptor (can be at class level)
    @ExpectedDataSet(value = "expectedUsers.yml",ignoreCols = "id")
    public void shouldMatchExpectedDataSet() {
        User u = new User();
        u.setName("expected user1");
        User u2 = new User();
        u2.setName("expected user2");
        em.getTransaction().begin();
        em.persist(u);
        em.persist(u2);
        em.getTransaction().commit();
    }
Since version 0.9.0 (To be released at the time of writing) transactions will be able to be managed automatically at test level (useful for expected datasets cause you don’t assert db changes inside the test), see example here.

9. Ruling database in BDD tests

BDD and DBUnit are integrated by DBUnit Rules Cucumber. It’s a Cucumber runner which is CDI aware.

9.1. Configuration

Just add following dependency to your classpath:

pom.xml
		<dependency>
			<groupId>com.github.dbunit-rules</groupId>
			<artifactId>cucumber</artifactId>
			<version>0.14.0</version>
			<scope>test</scope>
		</dependency>

Now you just need to use CdiCucumberTestRunner to have Cucumber, CDI and DBUnit on your BDD tests.

9.2. Example

First we need a feature file:

src/test/resources/features/search-users.feature
Feature: Search users
In order to find users quickly
As a recruiter
I want to be able to query users by its tweets.

Scenario Outline: Search users by tweet content

Given We have two users that have tweets in our database

When I search them by tweet content <value>

Then I should find <number> users
Examples:
| value    | number |
| "dbunit" | 1      |
| "rules"  | 2      |

Then a dataset to prepare our database:

src/test/resources/datasets/usersWithTweet.json
{
  "USER": [
    {
      "id": 1,
      "name": "@realpestano"
    },
    {
      "id": 2,
      "name": "@dbunit"
    }
  ],
  "TWEET": [
    {
      "id": 1,
      "content": "dbunit rules json example",
      "date": "2013-01-20",
      "user_id": 1
    },
    {
      "id": 2,
      "content": "CDI rules",
      "date": "2016-06-20",
      "user_id": 2
    }
  ]
}

Now a Cucumber runner test entry point:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.github.dbunit.rules.sample.bdd;

import com.github.dbunit.rules.cucumber.CdiCucumberTestRunner;
import cucumber.api.CucumberOptions;
import org.junit.runner.RunWith;

/**
 * Created by rmpestano on 4/17/16.
 */
@RunWith(CdiCucumberTestRunner.class)
@CucumberOptions(features ="src/test/resources/features/search-users.feature")
public class DBUnitRulesBddTest {
}

And finally our cucumber step definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.github.dbunit.rules.sample.bdd;

import com.github.dbunit.rules.api.dataset.DataSet;
import com.github.dbunit.rules.cdi.api.DBUnitInterceptor;
import com.github.dbunit.rules.sample.User;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.hibernate.Session;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Created by pestano on 20/06/16.
 */
@DBUnitInterceptor
public class SearchUsersSteps {

    @Inject
    EntityManager entityManager;

    List<User> usersFound;

    @Given("^We have two users that have tweets in our database$")
    @DataSet("usersWithTweet.json")
    public void We_have_two_users_in_our_database() throws Throwable {
    }

    @When("^I search them by tweet content \"([^\"]*)\"$")
    public void I_search_them_by_tweet_content_value(String tweetContent) throws Throwable {
        Session session = entityManager.unwrap(Session.class);
        usersFound = session.createCriteria(User.class).
        createAlias("tweets","tweets", JoinType.LEFT_OUTER_JOIN).
        add(Restrictions.ilike("tweets.content",tweetContent, MatchMode.ANYWHERE)).list();
    }

    @Then("^I should find (\\d+) users$")
    public void I_should_find_number_users(int numberOfUsersFound) throws Throwable {
        assertThat(usersFound).
                isNotNull().
                hasSize(numberOfUsersFound).
                contains(new User(1L));//examples contains user with id=1
    }


}
Living documentation of DBUnit Rules is based on its BDD tests, you can access it here: http://rmpestano.github.io/dbunit-rules/documentation.html.

10. Ruling database in JUnit 5 tests

JUnit 5 is the new version of JUnit and comes with a new extension model, so instead of rules you will use extensions in your tests. DBUnit Rules comes with a JUnit 5 extension which enables DBUnit.

10.1. Configuration

Just add following dependency to your classpath:

pom.xml
<dependency>
   <groupId>com.github.dbunit-rules</groupId>
   <artifactId>junit5</artifactId>
   <version>0.12.0</version>
   <scope>test</scope>
</dependency>

10.2. Example

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExtendWith(DBUnitExtension.class) (1)
@RunWith(JUnitPlatform.class) (2)
public class DBUnitJUnit5Test {

    private ConnectionHolder connectionHolder = () -> (3)
            instance("junit5-pu").connection(); (4)

    @Test
    @DataSet("users.yml")
    public void shouldListUsers() {
        List<User> users = em().createQuery("select u from User u").getResultList();
        assertThat(users).isNotNull().isNotEmpty().hasSize(2);
    }
1 Enables DBUnit;
2 JUnit 5 runner;
3 As JUnit5 requires Java8 you can use lambdas in your tests;
4 DBUnitExtension will get connection by reflection so just declare a field or a method with ConnectionHolder as return type.
Source code of the above example can be found here.

11. Exporting DataSets

Creating dataset files is a very error prone task when done manually. Using DataSetExporter component you can generate datasets in YML, JSON, XML, CSV and XLS formats:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    @Test
    @DataSet(cleanBefore=true)
    public void shouldExportYMLDataSetUsingDataSetExporter() throws SQLException, DatabaseUnitException{
            tx().begin();
            User u1 = new User();
            u1.setName("u1");
            em().persist(u1); /just insert a user and assert it is present in exported dataset
        tx().commit();
            tx().commit();
            DataSetExporterImpl.getInstance().
            export(new DatabaseConnection(emProvider.connection()), (1)
                new DataSetExportConfig().outputFileName("target/user.yml")); (2)
            File ymlDataSet = new File("target/user.yml");
          assertThat(ymlDataSet).exists();
          assertThat(contentOf(ymlDataSet)).
                  contains("USER:"+NEW_LINE +
                          "  - ID: 1"+NEW_LINE +
                          "    NAME: \"u1\""+NEW_LINE
                          );

    }
1 DatabaseConnection is from DBUnit api and only needs a JDBC connection;
2 the second required parameter is a ExporterCnfig which only requires output file name attribute;

You can use @ExportDataSet to make extraction even easier:

1
2
3
4
5
6
    @Test
    @DataSet("datasets/yml/users.yml") (1)
    @ExportDataSet(format = DataSetFormat.XML,outputName="target/exported/xml/allTables.xml")
    public void shouldExportAllTablesInXMLFormat() {

    }
1 Not required, its here only to add some data to be exported after test execution.
Full example above (and other related tests) can be found here.

11.1. Configuration

Following table shows all exporter configuration options:

Name Description Default

format

Exported dataset file format.

YML

includeTables

A list of table names to include in exported dataset.

Default is empty which means ALL tables.

queryList

A list of select statements which the result will used in exported dataset.

{}

dependentTables

If true will bring dependent tables of declared includeTables.

false

outputName

Name (and path) of output file.

""

12. Detecting connection leaks

DBUnit Rules provides a component that count JDBC connection before and after test execution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@RunWith(JUnit4.class)
@DBUnit(leakHunter = true)
public class LeakHunterIt {

    @Rule
    public DBUnitRule dbUnitRule = DBUnitRule.instance(new ConnectionHolderImpl(getConnection()));

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @BeforeClass
    public static void initDB() {
        //trigger db initialization
        Persistence.createEntityManagerFactory("rules-it");
    }

    @Test
    @DataSet("yml/user.yml")
    public void shouldFindConnectionLeak() {
         exception.expect(LeakHunterException.class); (1)
         exception.expectMessage("Execution of method shouldFindConnectionLeak left 1 open connection(s).");
         createLeak();
     }

     @Test
     @DataSet("yml/user.yml")
     public void shouldFindTwoConnectionLeaks()  {
         exception.expect(LeakHunterException.class);
         exception.expectMessage("Execution of method shouldFindTwoConnectionLeaks left 2 open connection(s).");
         createLeak();
         createLeak();
     }

     @Test
     @DataSet("yml/user.yml")
     @DBUnit(leakHunter = false)
     public void shouldNotFindConnectionLeakWhenHunterIsDisabled() {
           createLeak();
     }

    private Connection getConnection() {
          try {
               return DriverManager.getConnection("jdbc:hsqldb:mem:test;DB_CLOSE_DELAY=-1", "sa", "");
           } catch (SQLException e) {
               e.printStackTrace();
               return null;
           }
        }

     private void createLeak() throws SQLException {
         Connection connection = getConnection();
         try (Statement stmt = connection.createStatement()) {
              ResultSet resultSet = stmt.executeQuery("select count(*) from user");
              assertThat(resultSet.next()).isTrue();
              assertThat(resultSet.getInt(1)).isEqualTo(2);
           }
     }

}
1 If number of connections after test execution are greater than before then a LeakHunterException will be raised.

1. In the context of this article, database testing stands for JUnit integration tests which depend on a relational database so application business logic that depend on a database can be tested without mocking.