This guide is a 20~30 minutes read
which will introduce Database Rider and make a tour among its main features.
1. Introduction
Database Rider
integrates JUnit and DBUnit through JUnit rules and, in case of CDI based tests, a CDI interceptor. This powerful combination let you easily prepare the database state for testing through yaml, xml, json, xls or csv files.
Most inspiration of Database Rider 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/database-rider/getting-started
2. Setup Database Rider
First thing to do is to add Database Rider core module to your test classpath:
<dependency> <groupId>com.github.database-rider</groupId> <artifactId>rider-core</artifactId> <version>${project.version}</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>
Database Rider don’t depend on JPA, it only needs a JDBC connection to ride database through DBUnit. |
And the entity manager 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="riderDB" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.github.database.rider.gettingstarted.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 ride our database tests!
3. Example
Create a yaml file which will be used to prepare database (with two users) before the test:
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 indentation can lead to invalid dataset (principally in yaml datasets).
|
For more dataset examples, look here. |
You can generate datasets based on database tables, look at Exporting DataSets section. |
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 DatabaseRiderCoreTest {
@Rule
public EntityManagerProvider emProvider = EntityManagerProvider.instance("riderDB"); (1)
@Rule
public DBUnitRule dbUnitRule = DBUnitRule.instance(emProvider.connection()); (2)
@Test
@DataSet({"users.yml","empty-tweets.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. riderDB 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.database.rider.core.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) clear("riderDB"). (1)
em().createQuery("select u from User u where u.id = 1").
getSingleResult();
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("@realpestano");
tx().begin(); (2)
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 | clear("unitName") just clears JPA first level cache |
2 | tx() is a shortcut for the entity manager transaction provided by EntityManagerProvider. |
5. Database assertion with ExpectedDataSet
Consider the following datasets:
USER: (1) - ID: 1 (2) NAME: "@realpestano" - ID: 2 NAME: "@dbunit"
and expected dataset:
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 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.database.rider.assertion.DataSetAssert.compareData(DataSetAssert.java:94) |
5.1. Regular expressions
Expected datasets also alow regexp
in datasets:
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
Database Rider 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:
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) 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.database.rider.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:
<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:
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) 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. Configuration
There are two types of configuration in Database Rider: DataSet
and DBUnit
.
- DataSet Configuration
-
this basically setup the
dataset
which will be used. The only way to configure a dataset is using @DataSet annotation.It can be used at class or method level:
@Test @DataSet(value ="users.yml", strategy = SeedStrategy.UPDATE, disableConstraints = true,cleanAfter = true,transactional = true) public void shouldLoadDataSetConfigFromAnnotation(){ }
Here are possible values:
Name Description Default value
Dataset file name using test resources folder as root directory. Multiple, comma separated, dataset file names can be provided.
""
executorId
Name of dataset executor for the given dataset.
DataSetExecutorImpl.DEFAULT_EXECUTOR_ID
strategy
DataSet seed strategy. Possible values are: CLEAN_INSERT, INSERT, REFRESH and UPDATE.
CLEAN_INSERT, meaning that DBUnit will clean and then insert data in tables present on provided dataset.
useSequenceFiltering
If true dbunit will look at constraints and dataset to try to determine the correct ordering for the SQL statements.
true
tableOrdering
A list of table names used to reorder DELETE operations to prevent failures due to circular dependencies.
""
disableConstraints
Disable database constraints.
false
cleanBefore
If true Database Rider will try to delete database before test in a smart way by using table ordering and brute force.
false
cleanAfter
If true Database Rider will try to delete database after test in a smart way by using table ordering and brute force.
false
transactional
If true a transaction will be started before test and committed after test execution.
false
executeStatementsBefore
A list of jdbc statements to execute before test.
{}
executeStatementsAfter
A list of jdbc statements to execute after test.
{}
executeScriptsBefore
A list of sql script files to execute before test. Note that commands inside sql file must be separated by
;
.{}
executeScriptsAfter
A list of sql script files to execute after test. Note that commands inside sql file must be separated by
;
.{}
- DBUnit Configuration
-
this basically setup
DBUnit
itself. It can be configured by @DBUnit annotation (class or method level) and dbunit.yml file present in test resources folder.@Test @DBUnit(cacheConnection = true, cacheTableNames = false, allowEmptyFields = true,batchSize = 50) public void shouldLoadDBUnitConfigViaAnnotation() { }
Here is a dbunit.yml example, also the default values:
src/test/resources/dbunit.ymlcacheConnection: true cacheTableNames: true leakHunter: false properties: batchedStatements: false qualifiedTableNames: false caseSensitiveTableNames: false batchSize: 100 fetchSize: 100 allowEmptyFields: false escapePattern: connectionConfig: driver: "" url: "" user: "" password: ""
@DBUnit
annotation takes precedence overdbunit.yml
global configuration which will be used only if the annotation is not present.
8. 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.database.rider.gettingstarted;
import com.github.database.rider.core.DBUnitRule;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.DataSetExecutor;
import com.github.database.rider.core.configuration.DataSetConfig;
import com.github.database.rider.core.connection.ConnectionHolderImpl;
import com.github.database.rider.core.dataset.DataSetExecutorImpl;
import com.github.database.rider.core.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 Database Rider. |
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 instead of using annotations. |
Prefer RiderDSL to initiallize datassets programmatically. |
9. Riding 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. Database Rider comes with a JUnit 5 extension which enables DBUnit.
9.1. Configuration
Just add following dependency to your classpath:
<dependency> <groupId>com.github.database-rider</groupId> <artifactId>rider-junit5</artifactId> <version>1.19.0</version> <scope>test</scope> </dependency>
9.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. |
10. Riding database in CDI tests
For CDI based tests we are going to use DeltaSpike test control module and Database Rider CDI.
The first enables CDI in JUnit tests and the second enables DBUnit though a CDI interceptor.
10.1. Classpath dependencies
First we need DBUnit CDI: .pom.xml
<dependency> <groupId>com.github.database-rider</groupId> <artifactId>rider-cdi</artifactId> <version>${project.version}</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.core</groupId> <artifactId>deltaspike-core-api</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 |
10.2. Configuration
For configuration we will need a beans.xml which enables DBUnit CDI interceptor:
<?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.database.rider.cdi.DBUnitInterceptorImpl</class> </interceptors> </beans>
And apache-deltaspike.properties
to set our tests as CDI beans:
deltaspike.testcontrol.use_test_class_as_cdi_bean=true
The test itself must be a CDI bean so Database Rider can intercept it.
When using above configuration the JUnit @Before will not work as expected, see discussion here.
|
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
25
package com.github.database.rider.gettingstarted.cdi;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import static com.github.database.rider.core.util.EntityManagerProvider.instance;
/**
* Created by pestano on 09/10/15.
*/
@ApplicationScoped
public class EntityManagerProducer {
private EntityManager em;
@Produces
public EntityManager produce() {
return instance("riderDB").em();
}
}
This entityManager will be used as a bridge to JDBC connection needed by Database Rider.
10.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 DatabaseRiderCDITest {
@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:
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();
}
2
11. Riding database in BDD tests
BDD and DBUnit are integrated by Database Rider Cucumber. It’s a Cucumber runner which is CDI aware.
11.1. Configuration
Just add following dependency to your classpath:
<dependency> <groupId>com.github.database-rider</groupId> <artifactId>rider-cucumber</artifactId> <version>${project.version}</version> <scope>test</scope> </dependency>
Now you just need to use CdiCucumberTestRunner to have Cucumber
, CDI
and DBUnit
on your BDD tests.
11.2. Example
First we need a feature file:
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:
{ "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.database.rider.gettingstarted.bdd;
import com.github.database.rider.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 DatabaseRiderBddTest {
}
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.database.rider.gettingstarted.bdd;
import com.github.database.rider.cdi.api.DBUnitInterceptor;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.gettingstarted.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 Database Rider is based on its BDD tests, you can access it here: http://rmpestano.github.io/database-rider/latest/documentation.html. |
12. Exporting DataSets
Creating dataset files is a very error prone
task when done manually. Using DataSetExporter component you can generate datasets from database in YML
, JSON
, XML
, CSV
and XLS
formats.
There are two ways to generate datasets with database rider, programmatically
or from your IDE using a JBoss Forge addon
:
12.1. Generating datasets programmatically
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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();
DataSetExporterImpl.getInstance().
export(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 | JDBC connection; |
2 | the second required parameter is a ExporterConfig 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. |
12.2. Export Datasets using DBUnit Addon
DBUnit Addon is a JBoss forge addon which lets you export datasets from within your IDE through a nice GUI interface.
12.2.1. Installation
You will need Forge installed in your IDE or operating system. After that use install addon from git command:
addon-install-from-git --url https://github.com/database-rider/dbunit-addon.git
12.3. Configuration
Following table shows dataset 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. |
"" |
13. Detecting connection leaks
Database Rider provides a component that counts 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. |