First my setup:
A little context:
I have a REST web service that is deployed on a Tomcat webserver and that has a h2 database underneath. I have a REST service that doesn't have any POST/PUT methods. When writing an integration test for it, I manually added the entries in the DB using the H2 console and placed the h2-file on the server. Finally my integration test calls the REST service and the data I manually injected in the DB is returned and the test succeeds. This is not maintainable and it would be great to inject for every test the data I need (this approach can be used for other integration tests later...). The goal is to inject data into the same database as the application deployed on Tomcat is using.
I thought it would be very easy and I wrote an integration test reusing the same application context that is used on the server side:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="${database.url}" />
<property name="username" value="" />
<property name="password" value="" />
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="be.wiv_isp.healthdata.catalogue.domain" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
</props>
</property>
</bean>
<bean id="jpaVendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf" />
</bean>
where the database url is defined in a properties fil:
database.url=jdbc:h2:file:${healthdata.working.dir}/database/database-catalogue;AUTO_SERVER=true
Then I annotated the EntityManager as being my PersistenceContext and wrote a simple unit test:
@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:/applicationContext-it-test.xml"})
public class H2Test {
@PersistenceContext
private javax.persistence.EntityManager em;
DataCollectionDefinition dcd = new DataCollectionDefinitionBuilder()
.withId(Long.valueOf(1))
.build();
@Before
public void init() {
em.persist(dcd);
em.flush();
em.clear();
}
@Test
public void testGet() {
DataCollectionDefinition found = em.find(DataCollectionDefinition.class, 1);
Assert.assertEquals(found, dcd);
}
}
This test runs fine! However when I put a breakpoint after the data is flushed using the EntityManager and I connect to my H2 filesystem database, nothing is injected!
Now I was wondering. Is it normal that JUnit using Spring NEVER actually persists the data in the database and keeps it somewhere in memory? And is there a way to persist it anyway, so I could use it for prefilling of the database for my integration tests.
For now I have a workaround storing my data using good old JDBC, but it's dirty and I think it should work using Spring and above all I would like to understand why the data is not persisted using the EntityManager of Spring...
What's going on is a combination of how JPA works and what Spring does when running transactional tests.
First, JPA when an entity is persisted in JPA, that does not mean that the data is actually persisted to the database. Usually the data will be pushed to the database when the current transaction commits, or when you flush the entity manager. Check out this SO answer among the many you can find with a simple google search.
Second, when Spring runs a test that is annotated with @Transactional
, then by default it will rollback the transaction when the test completes. Check this of many similar issues. In order to override that default behavior, you need to use @Rollback(false)
.
The data gets written to the file when you use JDBC, because you are running the statements outside of the Transaction Spring has created for the test and therefore the default rollback has no data to revert.
Add @Commit at class level or method level. Check this
When declared as a class-level annotation, @Commit defines the default commit semantics for all test methods within the test class hierarchy. When declared as a method-level annotation, @Commit defines commit semantics for the specific test method, potentially overriding class-level default commit or rollback semantics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With