I think, you must do an integration test here with H2 or other in-memory database. As you said, if you only use mocks, you can see how object interacts with each other, but you never know what result list you get.
I am on the same page, not with Restriction
or so, but with JPA 2.0 CriteriaQuery
and CriteriaBuilder
. I build complex predicates in my persistence layer, and at last, I find it becomes inevitable to test with data in db, as no one knows what would be the final query in SQL. And I decide that in this part of the system, an integration is needed, so I went for it.
At last it is not very hard to build such a test. You need H2 dependency, a persistence.xml
like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- For H2 database integration tests. -->
<!-- For each int test, define unique name PU in this file and include SQL files in different paths. -->
<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="test-item-history-service-bean" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider> <!-- mind here: must be this! cannot be JPA provider! -->
<class>com.data.company.Company</class>
<class>com.data.company.ItemHistory</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle;INIT=RUNSCRIPT FROM 'src/test/resources/db/item-history/create.sql'\;RUNSCRIPT FROM 'src/test/resources/db/item-history/populate.sql'"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.id.new_generator_mappings" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/> <!-- mind here! Can only be "update"! "create-drop" will prevent data insertion! -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.default_schema" value="main"/>
</properties>
</persistence-unit>
</persistence>
(Mind very carefully the comment in the XML above, it took me a week to finally solve them)
Note about the provider: see here: How to configure JPA for testing in Maven
And in the two sql files, you CREATE TABLE ...
and INSERT INTO ...
. Insert whatever you like, as the data is part of the test.
And, a test like this:
/**
* Integration tests with in-memory H2 DB. Created because:
* - In-memory DB are relatively cheap to create and destroy, so these tests are quick
* - When using {@link javax.persistence.criteria.CriteriaQuery}, we inevitably introduce complex perdicates'
* construction into persistence layer, which is a drawback of it, but we cannot trade it with repetitive queries
* per id, which is a performance issue, so we need to find a way to test it
* - JBehave tests are for the user story flows, here we only want to check with the complex queries, certain
* records are returned; performance can be verified in UAM.
*/
@RunWith(MockitoJUnitRunner.class)
public class ItemHistoryPersistenceServiceBeanDBIntegrationTest {
private static EntityManagerFactory factory;
private EntityManager realEntityManager;
private ItemHistoryPersistenceServiceBean serviceBean;
private Query<String> inputQuery;
@BeforeClass
public static void prepare() {
factory = Persistence.createEntityManagerFactory("test-item-history-service-bean");
}
@Before
public void setup() {
realEntityManager = factory.createEntityManager();
EntityManager spy = spy(realEntityManager);
serviceBean = new ItemHistoryPersistenceServiceBean();
try {
// inject the real entity manager, instead of using mocks
Field entityManagerField = serviceBean.getClass().getDeclaredField("entityManager");
entityManagerField.setAccessible(true);
entityManagerField.set(serviceBean, spy);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError("should not reach here");
}
inputQuery = new Query<>();
inputQuery.setObjectId("itemId");
}
@After
public void teardown() {
realEntityManager.close();
}
@Test
public void findByIdAndToken_shouldReturnRecordsMatchingOnlyTokenFilter() {
try {
// when
List<ItemHistory> actual = serviceBean.findByIdAndToken(inputQuery);
// then
assertEquals(2, actual.size());
assertThat(actual.get(0).getItemPackageName(), anyOf(is("orgId 3.88"), is("orgId 3.99.3")));
assertThat(actual.get(1).getItemPackageName(), anyOf(is("orgId 3.88"), is("orgId 3.99.3")));
} catch (DataLookupException e) {
throw new AssertionError("should not reach here");
}
}
}