Implements JUnit 4 runner and rule, as well as JUnit 5 extension to enable easy testing of javax.persistence entities with an arbitrary persistence provider. Both JPA 2.0, as well as JPA 2.1 is supported (See Issues for limitations).
- Makes use of standard
@PersistenceContext
and@PersistenceUnit
annotations to inject theEntityManager
, respectivelyEntityManagerFactory
. - Solely relies on the JPA configuration (
persistence.xml
). No further JPA Unit specific configuration required. - Does not impose any JPA provider dependencies.
- Implements automatic transaction management.
- Enables JPA second level cache control
- Offers different strategies to
- seed the database using predefined data sets (depending on the used data base - defined in XML, JSON, YAML or SQL statements)
- cleanup the database before or after the actual test execution based on data sets or arbitrary scripts
- execute arbitrary scripts before and/or after test execution
- verify contents of the database after test execution
- Enables bootstrapping of the database schema and contents using plain data base statements (e.g. SQL) or arbitrary frameworks, like e.g. FlywayDB or Liquibase before the starting of JPA provider
- Implements seamless integration with CDI.
- Supports acceptance based testing using Cucumber
- Supports acceptance based testing using Concordion
- Supports SQL and NoSQL databases (based on what is possible with the chosen JPA provider). See below for a list of supported NoSQL databases and known limitations.
The implementation is inspired by the Arquillian Persistence Extension. Some of the code fragments are extracted out of it and adopted to suit the needs.
To be able to use the JPA Unit you will have to add some dependencies to your Maven project. For easier dependency management, there is a bom available which you can add to your dependencyManagement
section:
<dependencyManagement>
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-bom</artifactId>
<version>${jpa-unit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
The actual dependencies are listed in sections addressing the different possible integration types.
To work with JUnit 4, you would need to add jpa-unit4
to your test dependencies:
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit4</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
The basic requirements on the code level are the presence of either
- the
@RunWith(JpaUnitRunner.class)
annotation on the class level, or - the
JpaUnitRule
property, annotated with@Rule
Example using JpaUnitRunner
:
@RunWith(JpaUnitRunner.class)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
Example using JpaUnitRule
:
public class MyTest {
@Rule
public JpaUnitRule rule = new JpaUnitRule(getClass());
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
To work with JUnit 5, you would need to add jpa-unit5
to your test dependencies:
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit5</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
On the code level, there is no much choice for JUnit 5
- the test class needs to be annotated with
@ExtendWith(JpaUnit.class)
annotation.
Example:
@ExtendWith(JpaUnit.class)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
Irrespectively the JUnit version, the presence of either
- an
EntityManager
property annotated with@PersistenceContext
. In this case a newEntityManager
instance is acquired for each test case. On test case exit it is cleared and closed. Furthermore the usage of anEntityManager
instance managed by JPA Unit, enables automatic transaction management, where a new transaction is started before each test case and committed after the test case returns, respectively the method annotated with@After
. The@Transactional
annotation (see below) can be used to overwrite and configure the required behavior. - or an
EntityManagerFactory
property annotated with@PersistenceUnit
. In this case the user is responsible for obtaining and closing the requiredEntityManager
instance including the corresponding transaction management. There are however some utility functions which can ease the test implementation (seeTransactionSupport
class).
is required. Irrespective of the used configuration, the EntityManagerFactory
instance is acquired once and lives for the duration of the entire test suite implemented by the given test class.
In both cases the reference to the persistence unit is required as well (e.g. @PersistenceContext(unitName = "my-test-unit")
or @PersistenceUnit(unitName = "my-test-unit")
). Thus, given the presence of a persistence provider configuration, the examples, shown above, already implement full functional tests.
Like in any JPA application, you have to define a persistence.xml
file in the META-INF
directory which includes the JPA provider and persistence-unit
configuration.
For test purposes the transaction-type
of the configured persistence-unit
must be RESOURCE_LOCAL
.
It is possible to configure PersistenceProperties
within @PersistenceContext
to override values in persistence.xml
. In addition it is possible to use system properties to specify them from outside of the test.
For example:
-Dtest.jdbc.url=jdbc:oracle:thin:@myHost:1521:DB
@ExtendWith(JpaUnit.class)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit", properties = {@PersistenceProperty(name = "javax.persistence.jdbc.url", value = "${test.jdbc.url}")})
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
To control the test behavior, JPA Unit comes with a handful of annotations and some utility classes. All these annotations can be applied on class and method level, where the latter always takes precedence over the former. JPA Unit follows the concept of configuration by exception whenever possible. To support this concept its API consists mainly of annotations with meaningful defaults (if the annotation is not present) used to drive the test.
@ApplyScriptsBefore
, which can be used to define arbitrary scripts which shall be executed before running the test method.@ApplyScriptsAfter
, which can be used to define arbitrary scripts which shall be executed after running the test method.@Bootstrapping
, which can be used to define a method executed only once before the bootstrapping of a JPA provider happens. This can be handy e.g. to setup a test specific DB schema.@Cleanup
, which can be used to define when the database cleanup should be triggered.@CleanupCache
, which can be used to define whether and when the JPA L2 cache should be evicted.@CleanupUsingScripts
, which can be used to define arbitrary scripts which shall be used for cleaning the database.@ExpectedDataSets
, which provides the ability to verify the state of underlying database using data sets. Verification is invoked after test's execution.@InitialDataSets
, which provides the ability to seed the database using data sets before test method execution.@Transactional
, which can be used to control the automatic transaction management for a test if supported by the chosen JPA provider for the chosen database. Otherwise it does not have any effect.TransactionSupport
, comes in handy when fine grained transaction management is required or automatic transaction management is disabled. As for the@Transactional
annotation, if not supported by the chosen JPA provider for the chosen database, the usage of these functions has no effect.
All these elements are described in more detail below.
Like already written above automatic transaction management is active if the test uses an EntityManager
instance controlled by JPA Unit. To tweak the required behavior you can use the @Transactional
annotation either on a test class to apply the same behavior for all tests, or on a single test. This annotation has following properties:
value
of typeTransactionMode
. Following modes are available:COMMIT
. The test is wrapped in a transaction which is committed on return. This is the default behavior.DISABLED
. The transactional support is disabled.ROLLBACK
. Perform a rollback on test return.
Example which disables transactional support for all tests implemented by a given class:
@RunWith(JpaUnitRunner.class)
@Transactional(TransactionMode.DISABLED)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
TransactionSupport
becomes handy, when fine graned transaction management is desired or automatic transaction management is disabled (e.g. the test injects EntityManagerFactory
). Following methods are available:
newTransaction(EntityManager em)
is a static factory method to create newTransactionSupport
objectflushContextOnCommit(boolean flag)
can be used to configure theTransactionSupport
object to flush theEntityManager
after the transaction is committed.clearContextOnCommit(boolean flag)
can be used to configure theTransactionSupport
object to clear theEntityManager
after the transaction is committed.execute(<Expression>)
executes the given expression and wraps it in a new transaction. If the expression returns a result, it is returned to the caller. Following behavior is implemented:- Before the execution of
<Expression>
: If an active transaction is already running, it is committed and a new transaction is started. Otherwise just a new transaction is started. - After the execution of
<Expression>
: The transaction wrapping the<Expression>
is committed. If an active transaction was running and was committed before the<Expression>
wrapping transaction was started, a new transaction is started.
- Before the execution of
Here a usage example:
@RunWith(JpaUnitRunner.class)
@Transactional(TransactionMode.DISABLED)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
newTransaction(manager).execute(() -> {
// some code wrapped in a transaction
});
int result = newTransaction(manager)
.clearContextOnCommit(true)
.execute(() -> {
// some code wrapped in a transaction
return 1;
});
}
}
Creating ad-hoc object graphs in a test to seed the database can be a complex task on the one hand and made the test less readable. On the other hand it is usually not the goal of a test case, rather a prerequisite. To address this, JPA Unit provides an alternative way in a form of database fixtures, which are easy configurable and can be applied for all tests or for a single test. To achieve this JPA Unit uses the concept of data sets. In essence, data sets are files containing data to be inserted into the database. Since data sets are database specific, see the corresponding database specific sections for details on supported types and formats.
To seed the database using data set files put the @InitialDataSets
annotation either on the test itself or on the test class. This annotation has following properties:
value
of typeString[]
which takes a list of data set files used to seed the database.seedStrategy
of typeDataSeedStrategy
which can be used to defined the seeding strategy. Following strategies are available:CLEAN_INSERT
. Performs insert of the data defined in provided data sets, after removal of all data present in the tables referred in provided files.INSERT
. Performs insert of the data defined in provided data sets. This is the default strategy.REFRESH
. During this operation existing rows are updated and new ones are inserted. Entries already existing in the database which are not defined in the provided data set are not affected.UPDATE
. This strategy updates existing rows using data provided in the data sets. If data set contain a row which is not present in the database (identified by its primary key) then exception is thrown.
Usage example:
@RunWith(JpaUnitRunner.class)
@InitialDataSets("datasets/initial-data.json")
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
Seeding the database as described above introduces an additional abstraction level, which is not always desired on one hand. On other hand, there might be a need to disable specific database constraint checks before a database cleanup might be performed (latter only possible in a post test execution step). Usage of plain scripts (e.g. SQL) comes in handy here to execute any action directly on the database level. Simply put @ApplyScriptBefore
and/or @ApplyScriptAfter
annotation on your test class and/or directly on your test method. Corresponding scripts will be executed before and/or after test method accordingly. If there is definition on both, test method level annotation takes precedence.
Both annotation have the following properties:
value
of typeString[]
which needs to be set to reference the required database specific scripts (e.g. SQL for a relational database).
Usage example:
@RunWith(JpaUnitRunner.class)
@ApplyScriptBefore("scrips/some-script.script")
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
@ApplyScriptAfter({
"scrips/some-other-script-1.script",
"scrips/some-other-script-2.script"
})
public void someTest() {
// your code here
}
}
Asserting database state directly from testing code might imply a huge amount of work. @ExpectedDataSets
comes in handy here. Just put this annotation either on a test class to apply the same assertions for all tests, or on a single test method (the latter takes precedence) and JPA Unit will use the referenced files to check whether the database contains entries you are expecting after the test execution.
The @ExpectedDataSets
annotation has the following properties:
value
of typeString[]
which takes a list of data set files used for post-test verification of the database's state.orderBy
of typeString[]
which takes a list of columns to be used for sorting rows to determine the order of data sets comparison (Not supported by all database types).excludeColumns
of typeString[]
which takes a list of columns to be excluded during the comparison.filter
of typeClass<?>[]
which takes a list of custom filters to be applied during verification in the specified order (Not supported by all database types)strict
of typeboolean
which defines whether the performed verification about expected data sets is strict or not. In strict mode all tables/collections and entries not defined in the expected data sets are considered to be an error. This is the default strategy.
Both orderBy
and excludeColumns
properties can be used to define columns with and without dotted notation. With dotted notation one can explicitly define the table/collection in addition to the actual field/property (see also the example below).
Usage example:
@RunWith(JpaUnitRunner.class)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
private Depositor depositor;
@Before
public void createTestData() throws OperationNotSupportedException {
depositor = new Depositor("Max", "Doe");
depositor.addContactDetail(new ContactDetail(ContactType.EMAIL, "john.doe@acme.com"));
depositor.addContactDetail(new ContactDetail(ContactType.TELEPHONE, "+1 22 2222 2222"));
depositor.addContactDetail(new ContactDetail(ContactType.MOBILE, "+1 11 1111 1111"));
final InstantAccessAccount instantAccessAccount = new InstantAccessAccount(depositor);
final GiroAccount giroAccount = new GiroAccount(depositor);
giroAccount.setCreditLimit(1000.0f);
giroAccount.deposit(100.0f);
giroAccount.transfer(150.0f, instantAccessAccount);
}
@Test
@ExpectedDataSets(value = "datasets/expected-data.json", excludeColumns = {
"ID", "DEPOSITOR_ID", "ACCOUNT_ID", "VERSION"
}, orderBy = {
"CONTACT_DETAIL.TYPE", "ACCOUNT_ENTRY.TYPE"
})
public void test1() {
manager.persist(depositor);
}
}
By default the database content is entirely erased before each test. If you want to control this behavior, @Cleanup
annotation is your friend. It defines when database cleanup should be triggered and which cleanup strategy to apply.
As always, you can use this annotation globally on a class level or on a method level. The latter takes precedence.
The @Cleanup
annotation has the following properties:
strategy
of typeCleanupStrategy
. Defines which strategy to apply while erasing the database content. Following strategies are available:STRICT
. Cleans entire database. This is the default strategy. Might require turning off database constraints (e.g. referential integrity).USED_ROWS_ONLY
. Deletes only those entries which were defined in data sets.USED_TABLES_ONLY
. Deletes only those tables/collections which were used in data sets.
phase
of typeCleanupPhase
. Defines the phase when the database cleanup should be triggered. Following phases are available:BEFORE
. The contents of database are deleted (based on the strategy) before the test method is executed.AFTER
. The contents of database are deleted (based on the strategy) after the test method is executed. This is the default phase.NONE
. The cleanup of the database is disabled.
Usage example:
@RunWith(JpaUnitRunner.class)
@Cleanup(TransactionMode.BEFORE) // change the default phase from AFTER to BEFORE
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
@Test
@Cleanup(phase = CleanupPhase.AFTER, strategy = CleanupStrategy.USED_ROWS_ONLY)
public void otherTest() {
// your code here
}
}
If automatic cleanup as described in Strategy based Cleanup does not suit your needs, @CleanupUsingScripts
might be your friend. You can use it to execute custom scripts to clean your database before or after the test. Just put this annotation either on the test itself or on the test class. As always the definition applied on the test method level takes precedence.
This annotation has following properties:
value
of typeString[]
which needs to be set to reference the required database specific cleanup scripts.phase
of typeCleanupPhase
. Defines the phase when the cleanup scripts should be executed. Following phases are available:BEFORE
. Before the test method is executed.AFTER
. After the test method is executed. This is the default phase.NONE
. The execution of scripts is disabled.
Usage example:
@RunWith(JpaUnitRunner.class)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
@CleanupUsingScripts(phase = CleanupPhase.AFTER, value = "scripts/delete-all.script")
public void otherTest() {
// your code here
}
}
The JPA L2 cache can be a two-edged sword if configured or used improperly. Therefore it is crucial to test the corresponding behavior as early as possible. JPA Unit enables this by the usage of the @CleanupCache
annotation either on a test class, to apply the same behavior for all tests, or on a single test level to define whether and when the JPA L2 cache should be evicted . Please note: The behavior of the second level can be configured in the persistence.xml
. If @CleanupCache
is used and the defined phase
(see below) is not NONE
, the second level cache will be evicted regardless the settings defined in the persistence.xml
. This annotation has following properties:
phase
of typeCleanupPhase
. Defines the phase when the second level cache cleanup should be triggered. Following phases are available:BEFORE
. The L2 cache is evicted before the test method is executed.AFTER
. The L2 cache is evicted after the test method is executed. This is the default phase.NONE
. The eviction of the L2 cache is disabled.
Example which evicts the JPA L2 cache before the execution of each test method implemented by a given class:
@RunWith(JpaUnitRunner.class)
@CleanupCache(TransactionMode.BEFORE)
public class MyTest {
@PersistenceContext(unitName = "my-test-unit")
private EntityManager manager;
@Test
public void someTest() {
// your code here
}
}
Bootstrapping of the data base schema, as well as the handling of its evolution over a period of time is a crucial topic. To enable a data base schema & contents setup close to the productive environment in which the JPA provider usually relies on this given DB setup, the corresponding database specific actions need to be done before the JPA provider is loaded by accessing the data base directly. JPA Unit enables this by the usage of the @Bootstrapping
annotation. A dedicated method of a test class, which implements a data base scheme & contents setup can be annotated with this annotation and is required to have one parameter of type DataSource
. JPA Unit will execute this method very early in its bootstrapping process. Because of this neither EntityManager
nor EntityManagerFactory
cannot be used at this time.
For tests, which use this feature, the JPA provider should be configured not to drop and create the data base schema on start, rather to verify it. For e.g. Hibernate this can be achieved by setting the hibernate.hbm2ddl.auto
property to the value validate
.
Usage example (bootstrapping with FlywayDB):
@RunWith(JpaUnitRunner.class)
public class FlywaydbTest {
@PersistenceContext(unitName = "my-verification-unit")
private EntityManager manager;
@Bootstrapping
public void prepareDataBase(final DataSource ds) {
// creates db schema and puts some data
final Flyway flyway = new Flyway();
flyway.setDataSource(ds);
flyway.clean();
flyway.migrate();
}
@Test
public void someTest() {
// your test specific code here
}
}
Depending on the used database, you will have to add a dependency for a database specific JPA-Unit plugin.
For all relational databases the jpa-unit-rdbms
dependency needs to be added:
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-rdbms</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
Here JPA Unit makes use of the standard
javax.persistence.jdbc.driver
,javax.persistence.jdbc.url
,javax.persistence.jdbc.user
andjavax.persistence.jdbc.password
The last two are optional (depending on the requirements of the underlying database).
properties to access the database directly.
Here an example of a persistence.xml
file which configures EclipseLink and H2 database:
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="my-test-unit" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- your classes converters, etc -->
<properties>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.target-database"
value="org.eclipse.persistence.platform.database.H2Platform" />
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:h2:mem:serviceEnablerDB;DB_CLOSE_DELAY=-1" />
<property name="javax.persistence.jdbc.user" value="test" />
<property name="javax.persistence.jdbc.password" value="test" />
</properties>
</persistence-unit>
</persistence>
Thanks to DBUnit, which is used internally for all RDBMS, following data set formats are supported:
- XML (Flat XML Data Set). A simple XML structure, where each element represents a single row in a given table and attribute names correspond to the table columns as illustrated below.
- YAML. Similar to the flat XML layout, but has some improvements (columns are calculated by parsing the entire data set, not just the first row)
- JSON. Similar to YAML.
- XSL(X). With this data set format each sheet represents a table. The first row of a sheet defines the columns names and remaining rows contains the data.
- CSV. Here a data set can be constructed from a directory containing csv files, each representing a separate table with its entries.
Here some data set examples:
<dataset>
<DEPOSITOR id="100" version="1" name="John" surname="Doe" />
<ADDRESS id="100" city="SomeCity" country="SomeCountry" street="SomeStreet 1"
zip_code="12345" owner_id="100"/>
<ADDRESS id="101" city="SomeOtherCity" country="SomeOtherCountry" street="SomeStreet 2"
zip_code="54321" owner_id="100"/>
<dataset>
DEPOSITOR:
- id: 100
version: 1
name: John
surname: Doe
ADDRESS:
- id: 100
city: SomeCity
country: SomeCountry
street: SomeStreet 1
zip_code: 12345
owner_id: 100
- id: 101
city: SomeOtherCity
country: SomeOtherCountry
street: SomeStreet 2
zip_code: 54321
owner_id: 100
"DEPOSITOR": [
{ "id": "100", "version": "1", "name": "John", "surname": "Doe" }
],
"ADDRESS": [
{ "id":"100", "city":"SomeCity", "country": "SomeCountry", "street": "SomeStreet 1",
"zip_code": "12345", "owner_id": "100" },
{ "id":"101", "city":"SomeOtherCity", "country": "SomeOtherCountry", "street": "SomeStreet 2",
"zip_code": "54321", "owner_id": "100" }
]
All DBUnit specific settings can be configured by just making a dbunit.properties
file available in the classpath. Long and short property names are supported.
Please note, that JPA-Unit configures the required DBUnit datatypeFactory
and metadataHandler
automatically based on the used JDBC driver.
For MongoDB, the jpa-unit-mongodb
dependency needs to be added:
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-mongodb</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
JPA Unit needs to connect to a running MongoDB instance. This is done using mongo-java-driver. Usage of an in-process, in-memory MongoDB implementations, like Fongo is not possible. To overcome this limitation, or made it at least less painful, one can use e.g.
- Flapdoodle Embedded MongoDB for the lifecycle management of a MongoDB instance from code, e.g. from
@BeforeClass
and@AfterClass
annotated methods. You can find working example within the JPA Unit integration test project for MongoDB. - embedmongo-maven-plugin for the lifecycle management of a MongoDB instance through Maven.
Since JPA does not address NoSQL databases, each JPA provider defines its own properties. These properties are also the only dependencies to a specific JPA provider implementation. As of todays JPA Unit MongoDB extension can use the properties of the following JPA provider:
- Hibernate OGM (with MongoDB extension).
- EclipseLink (with MongoDB extension)
- DataNucleus (with MongoDB extension)
- Kundera (with MongoDB extension)
Default data set format for MongoDB is JSON. In a simple case it must comply with the following example structure:
{
"collection_name_1": [
{
"property_1": "value_1",
"property_2": "value_2"
},
{
"property_3": NumberLong(10),
"property_4": { "$date": "2017-06-07T15:19:10.460Z" }
}
],
"collection_name_2": [
{
"property_5": 4,
"property_7": "value_7"
}
]
}
If indexes (for more information on MongoDB indexes and types see MongoDB Indexes) need to be included as well, the following structure applies:
{
"collection_name_1": {
"indexes": [
{
"index": {
"property_1": 1
},
"index": {
"property_2": 1,
"options": {
"unique": true,
"default_language": "english"
}
}
},
{
"index": {
"property_3": 1,
"property_4": -1
}
}
],
"data": [
{
"property_1": "value_1",
"property_2": "value_2"
},
{
"property_3": NumberLong(10),
"property_4": { "$date": "2017-06-07T15:19:10.460Z" }
}
]
}
}
Please note, that in this case the collection document consists of two subdocuments. The first one - indexes
is where the indexes are defined. Basically this is which fields of the collection are going to be indexed.
The second one - data
, where all documents, which belong to the collection under test, are defined. In both cases all the types defined by MongoDB are supported.
For Neo4j, the jpa-unit-neo4j
dependency needs to be added:
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-neo4j</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
JPA Unit needs to connect to a running Neo4j instance. This is done using Neo4j JDBC Driver, which as a neat side effect makes bootstrapping (see Bootstrapping of DB Schema & Contents) of the DB possible e.g. using LIQUIGRAPH. Usage of an in-process, in-memory Neo4j implementation is only possible if the embedded data base is configured to expose BOLT or HTTP interfaces. This can be achieved by the use of e.g.
- Neo4J Harness for the lifecycle management of a Neo4j instance from code, e.g. for test purposes by using a
Neo4jRule
. You can find working example as part of integration tests of JPA-Unit's neo4j project.
Since JPA does not address NoSQL databases, each JPA provider defines its own properties. These properties are also the only dependencies to a specific JPA provider implementation. As of todays JPA Unit Node4j extension can use the properties of the following JPA provider:
- Hibernate OGM (with Neo4j extension).
- DataNucleus (with Neo4j extension)
- Kundera (with Neo4j extension)
Even the last two support Neo4j in an embedded mode only, both can be used if the embedded Neo4j database exposes HTTP or BOLT interfaces (like available with Neo4j Harness). Since there is no possibility to define the corresponding
interfaces (BOLT or HTTP) in a standard way (by the means of the regular persistence.xml
), JPA-Unit makes use of a jpa-unit.properties
file, which has to be made available on the class path and which has to define the following
properties:
connection.url
, which defines the url to the available interface. E.g.jdbc:neo4j:bolt://localhost:7687
. This property is mandatory.user.name
, which defines the user name to be used during connection establishment. This property is optional.user.password
, which defines the password of the user to be used during connection establishment. This property is optional as well.
Same approach can be used if Hibernate OGM Neo4j extension is used in embedded mode.
A special note on Kundera: It still depends on a pretty old Neo4j (1.8.1) version. So even JPA-Unit's neo4j extension understands the configuration dialect of Kundera, I didn't find a version of Noe4j Harness, which can expose BOLT or HTTP protocols and is binary compatible with the version used by Kundera. With other words, as long as Kundera is not updated to use a more recent version of Neo4j, the usage of this JPA provider will most probably be not possible.
Thanks to jgrapht, which is used internally for graph handling, following data set formats are supported:
- GraphML. an XML-based file format for graphs.
If you want to generate/export data out of an existing Neo4j instance APOC can be really helpful.
Be however aware, that the data exported by APOC does not fully comply with GraphML. APOC generated file does not include key
elements definitions for
label
and labels
data
elements for edge
, respectively node
elements. It also adds additional attributes (label
and labels
) to node
and edge
elements which are not defined by GraphML. The first one needs to be addressed by adding the missing <key id="labels" for="node" attr.name="labels" attr.type="string"/>
and <key id="label" for="edge" attr.name="label" attr.type="string"/>
to the graph
element. The second one can be ignored - schema compliance is not enforced by
JPA-Unit's neo4j extension.
Here's an example:
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="name" for="node" attr.name="name" attr.type="string"/>
<key id="id" for="node" attr.name="id" attr.type="long"/>
<key id="labels" for="node" attr.name="labels" attr.type="string"/>
<key id="label" for="edge" attr.name="label" attr.type="string"/>
<graph id="G" edgedefault="directed">
<node id="1" labels=":Person">
<data key="labels">:Person</data>
<data key="name">Tom</data>
<data key="id">1</data>
</node>
<node id="2" labels=":Person">
<data key="labels">:Person</data>
<data key="name">Jerry</data>
<data key="id">2</data>
</node>
<edge id="3" source="1" target="2" label="friend">
<data key="label">friend</data>
</edge>
</graph>
</graphml>
To be able to use the JPA Unit with CDI, all you need in addition to your CDI test dependency, like DeltaSpike Test-Control Module or Gunnar's CDI Test, is to add the following dependency to your Maven project :
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-cdi</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
This dependency implements a CDI extension, which proxies the configured EntityManager
producer. During a JPA Unit test run it uses the EntityManager
configured in the test class instance. In all other cases it just uses the proxied producer.
Usage example:
@RunWith(CdiTestRunner.class)
public class CdiEnabledJpaUnitTest {
@Rule
public JpaUnitRule rule = new JpaUnitRule(getClass());
@PersistenceContext(unitName = "my-test-unit")
private static EntityManager manager;
@Inject
private SomeRepository repo;
@Test
public void someTest() {
// use CDI managed objects, like the repo from above
}
}
Cucumber is a BDD test framework. To be able to use JPA Unit with it, all you need in addition to cucumber dependencies is to add the following dependency to your Maven project :
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-cucumber</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
This dependency implements a Cucumber extension (ObjectFactory
) which intercepts all cucumber feature glue methods to enable the usage of JPA Unit annotations.
Since each feature/scenario glue, compared to regular JUnit tests, implements a single test specification, JPA Unit disables automatic data base cleanup. To avoid stale data
between the executions of different scenarios or more general different tests, you should take care of the cleanup by yourself. This is the only difference to the regular behavior.
This cleanup can be achieved, e.g. using the @Cleanup
annotation on e.g. a method annotated with the cucumber @After
annotation.
Analogue to regular JUnit tests a cucumber glue needs to reference either an EntityManager
or an EntityManagerFactory
. The EntityManagerFactory
lives for the
duration of the scenario execution.
Same rules as for regular JUnit tests apply for the EntityManager
as well: An EntityManager
for TRANSACTION
PersistenceContextType
lives only for the duration
of the execution of the glue method. An EntityManager
for EXTENDED
PersistenceContextType
has the life time of the EntityManagerFactory
and is closed after the
last glue method is executed. Latter configuration might be a better choice for cucumber glue.
Usage example:
@RunWith(Cucumber.class)
public class CucumberTest {
// According to cucumber, this class should not implement any tests
}
public class CucumberGlue {
@Rule
public JpaUnitRule rule = new JpaUnitRule(getClass());
@PersistenceContext(unitName = "my-test-unit", type = PersistenceContextType.EXTENDED)
private EntityManager manager;
@Given("^an some existing data in the db$")
@InitialDataSets("datasets/initial-data.json")
public void seedDatabase() {
// data base is seeded thanks to the jpa unit annotation
}
@When("^a new object is inserted$")
public void updataData() {
// e.g. manager.persist(new SomeEntity());
}
@Then("^it is expected in the db")
@ExpectedDataSets(value = "datasets/expected-data.json")
public void verifyData() {
// db verification is done thanks to the jpa unit annotation
}
@After
@Cleanup
public void cleanupDb() {
// to avoid stale data
}
}
If you would like to use cucumber with JPA Unit and CDI, you will have to follow the requirements from CDI Integration. However, since the usage of multiple runners is not possible, you'll have to start the CDI container manually. Here an example with Deltaspike:
@RunWith(Cucumber.class)
public class CucumberTest {
// According to cucumber, this class should not implement any tests
// but we can implement global setup and tear down functionality here.
private static CdiContainer cdiContainer;
@BeforeClass
public static void startContainer() {
cdiContainer = CdiContainerLoader.getCdiContainer();
cdiContainer.boot();
}
@AfterClass
public static void stopContainer() {
cdiContainer.shutdown();
}
}
Now you can inject your dependencies into glue objects.
Concordion is a BDD test framework. To be able to use JPA Unit with it, all you need in addition to concordion dependencies is to add the following dependency to your Maven project :
<dependency>
<groupId>com.github.dadrus.jpa-unit</groupId>
<artifactId>jpa-unit-concordion</artifactId>
<version>${jpa-unit.version}</version>
</dependency>
This dependency implements a specialized ConcordionRunner
- the JpaUnitConcordionRunner
which you have to use with the JUnit @RunWith
annotation on the class level.
This runner hooks into concordion implementation and intercepts all fixture methods referenced from corresponding specifications, thus enables the usage of JPA Unit annotations
on fixture methods.
Since each fixture/specification(-example), compared to a regular JUnit tests, implements a single test specification, JPA Unit disables automatic data base cleanup. To avoid stale data
between the executions of different scenarios or more general different tests, you should take care of the cleanup by yourself. This is the only difference to the regular behavior.
This cleanup can be achieved, e.g. using the @Cleanup
annotation on e.g. a method annotated with the concordion @AfterSpecification
annotation.
Analogue to regular JUnit tests a concordion fixture needs to reference either an EntityManager
or an EntityManagerFactory
. The EntityManagerFactory
lives for the
duration of the scenario execution.
Same rules as for regular JUnit tests apply for the EntityManager
as well: An EntityManager
for TRANSACTION
PersistenceContextType
lives only for the duration
of the execution of the fixture method. An EntityManager
for EXTENDED
PersistenceContextType
has the life time of the EntityManagerFactory
and is closed after the
last fixture method is executed. Latter configuration might be a better choice for concordion fixtures.
Usage example:
@RunWith(JpaUnitConcordionRunner.class)
public class ConcordionFixture {
@PersistenceContext(unitName = "my-test-unit", type = PersistenceContextType.EXTENDED)
private EntityManager manager;
public Depositor createNewCustomer(final String customerName) {
final String[] nameParts = customerName.split(" ");
final Depositor depositor = new Depositor(nameParts[0], nameParts[1]);
new InstantAccessAccount(depositor);
return depositor;
}
public void finalizeOnboarding(final Depositor depositor) {
manager.persist(depositor);
}
@ExpectedDataSets(value = "datasets/max-payne-data.json", excludeColumns = {
"ID", "DEPOSITOR_ID", "ACCOUNT_ID", "VERSION", "accounts"
})
@Cleanup(phase = CleanupPhase.AFTER)
public void verifyExistenceOfExpectedObjects() {
// The check is done via @ExpectedDataSets annotation
}
}
The associated specification looks like this:
# Create New Depositor
During the on-boarding of a new banking customer for an instant access account a new depositor as well
as a new instant access account have to be created.
### [Example](- "Onboard a new customer")
Given a new customer *[Max Payne](- "#customer = createNewCustomer(#TEXT)")*, applying for an instant
access account
When the [onboarding process completes](- "finalizeOnboarding(#customer)")
Then a new depositor object and a new instant access account object are
[present in the system](- "verifyExistenceOfExpectedObjects()").
If you would like to use concordion with JPA Unit and CDI, you will have to follow the requirements from CDI Integration. However, since the usage of multiple runners is not possible, you'll have to start the CDI container manually. Here's an excerpt demonstrating CDI usage:
@RunWith(JpaUnitConcordionRunner.class)
public class ConcordionFixture {
private static CdiContainer cdiContainer;
@BeforeSpecification
public static void startContainer() {
cdiContainer = CdiContainerLoader.getCdiContainer();
cdiContainer.boot();
}
@AfterSpecification
public static void stopContainer() {
cdiContainer.shutdown();
}
// ...
}
Now you can inject your dependencies into the fixture class.
If you organize your fixtures/specifications in a hierarchical story, it will make more sense to start and stop the CDI container in the root fixture
using methods annotated with @BeforeSuite
, respectively @AfterSuite
.