Permalink
Browse files

Merge pull request #1669 from dropwizard/dao_test_rule

JUnit rule for testing database interactions (redux)
(cherry picked from commit ea6656b)
  • Loading branch information...
jplock authored and joschi committed Aug 27, 2016
1 parent 64b942c commit ec7b6321e32c33cab4838b3bdaddc8127458fde4
@@ -414,7 +414,7 @@ before the command is ran.
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private final InputStream originalIn = System.in;
private final ByteArrayOutputStream stdOut = new ByteArrayOutputStream();
private final ByteArrayOutputStream stdErr = new ByteArrayOutputStream();
private Cli cli;
@@ -457,3 +457,54 @@ before the command is ran.
softly.assertAll();
}
}
.. _man-testing-database-interactions:
Testing Database Interactions
=============================
In Dropwizard, the database access is managed via the ``@UnitOfWork`` annotation used on resource
methods. In case you want to test database-layer code independently, a ``DAOTestRule`` is provided
which setups a Hibernate ``SessionFactory``.
.. code-block:: java
public class DatabaseTest {
@Rule
public DAOTestRule database = DAOTestRule.newBuilder().addEntityClass(FooEntity.class).build();
private FooDAO fooDAO;
@Before
public void setUp() {
fooDAO = new FooDAO(database.getSessionFactory());
}
@Test
public createsFoo() {
FooEntity fooEntity = new FooEntity("bar");
long id = database.inTransaction(() -> {
return fooDAO.save(fooEntity);
});
assertThat(fooEntity.getId, notNullValue());
}
@Test
public roundtripsFoo() {
long id = database.inTransaction(() -> {
return fooDAO.save(new FooEntity("baz"));
});
FooEntity fooEntity = fooDAO.get(id);
assertThat(fooEntity.getFoo(), equalTo("baz"));
}
}
The ``DAOTestRule``
* Creates a simple default Hibernate configuration using an H2 in-memory database
* Provides a ``SessionFactory`` instance which can be passed to, e.g., a subclass of ``AbstractDAO``
* Provides a function for executing database operations within a transaction
@@ -0,0 +1,55 @@
package com.example.helloworld.db;
import com.example.helloworld.core.Person;
import io.dropwizard.testing.junit.DAOTestRule;
import org.hibernate.exception.ConstraintViolationException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class PersonDAOTest {
@Rule
public DAOTestRule daoTestRule = DAOTestRule.newBuilder()
.addEntityClass(Person.class)
.build();
private PersonDAO personDAO;
@Before
public void setUp() throws Exception {
personDAO = new PersonDAO(daoTestRule.getSessionFactory());
}
@Test
public void createPerson() {
final Person jeff = daoTestRule.inTransaction(() -> personDAO.create(new Person("Jeff", "The plumber")));
assertThat(jeff.getId()).isGreaterThan(0);
assertThat(jeff.getFullName()).isEqualTo("Jeff");
assertThat(jeff.getJobTitle()).isEqualTo("The plumber");
assertThat(personDAO.findById(jeff.getId())).isEqualTo(Optional.of(jeff));
}
@Test
public void findAll() {
daoTestRule.inTransaction(() -> {
personDAO.create(new Person("Jeff", "The plumber"));
personDAO.create(new Person("Jim", "The cook"));
personDAO.create(new Person("Randy", "The watchman"));
});
final List<Person> persons = personDAO.findAll();
assertThat(persons).extracting("fullName").containsOnly("Jeff", "Jim", "Randy");
assertThat(persons).extracting("jobTitle").containsOnly("The plumber", "The cook", "The watchman");
}
@Test(expected = ConstraintViolationException.class)
public void handlesNullFullName() {
daoTestRule.inTransaction(() -> personDAO.create(new Person(null, "The null")));
}
}
View
@@ -57,5 +57,15 @@
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,215 @@
package io.dropwizard.testing.junit;
import com.google.common.base.Throwables;
import io.dropwizard.logging.BootstrapLogging;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.context.internal.ManagedSessionContext;
import org.junit.rules.ExternalResource;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
/**
* A JUnit rule for testing DAOs and Hibernate entities. It allows to quickly
* test the database access code without starting the Dropwizard infrastructure.
* <p>
* Example:
* <pre><code>
* {@literal @}Rule
public DAOTestRule daoTestRule = DAOTestRule.newBuilder()
.addEntityClass(Person.class)
.build();
private PersonDAO personDAO;
{@literal @}Before
public void setUp() throws Exception {
personDAO = new PersonDAO(daoTestRule.getSessionFactory());
}
{@literal @}Test
public void createPerson() {
Person wizard = daoTestRule.inTransaction(() -> personDAO.create(new Person("Merlin", "The chief wizard")));
assertThat(wizard.getId()).isGreaterThan(0);
assertThat(wizard.getFullName()).isEqualTo("Merlin");
assertThat(wizard.getJobTitle()).isEqualTo("The chief wizard");
}
* </code></pre>
* </p>
*/
public class DAOTestRule extends ExternalResource {
static {
BootstrapLogging.bootstrap();
}
public static class Builder {
private String url = "jdbc:h2:mem:" + UUID.randomUUID();
private String username = "sa";
private String password = "";
private String driver = "org.h2.Driver";
private String hbm2ddlAuto = "create";
private boolean showSql = false;
private boolean useSqlComments = false;
private Set<Class<?>> entityClasses = new LinkedHashSet<>();
private Map<String, String> properties = new HashMap<>();
public Builder setUrl(String url) {
this.url = url;
return this;
}
public Builder setUsername(String username) {
this.username = username;
return this;
}
public Builder setDriver(Class<? extends java.sql.Driver> driver) {
this.driver = driver.getName();
return this;
}
public Builder setHbm2DdlAuto(String hbm2ddlAuto) {
this.hbm2ddlAuto = hbm2ddlAuto;
return this;
}
public Builder setShowSql(boolean showSql) {
this.showSql = showSql;
return this;
}
public Builder useSqlComments(boolean useSqlComments) {
this.useSqlComments = useSqlComments;
return this;
}
public Builder addEntityClass(Class<?> entityClass) {
this.entityClasses.add(entityClass);
return this;
}
public Builder setProperty(String key, String value) {
this.properties.put(key, value);
return this;
}
public DAOTestRule build() {
final Configuration config = new Configuration();
config.setProperty(AvailableSettings.URL, url);
config.setProperty(AvailableSettings.USER, username);
config.setProperty(AvailableSettings.PASS, password);
config.setProperty(AvailableSettings.DRIVER, driver);
config.setProperty(AvailableSettings.HBM2DDL_AUTO, hbm2ddlAuto);
config.setProperty(AvailableSettings.SHOW_SQL, String.valueOf(showSql));
config.setProperty(AvailableSettings.USE_SQL_COMMENTS, String.valueOf(useSqlComments));
// Use the same configuration as in the Hibernate bundle to reduce differences between
// testing and production environments.
config.setProperty(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "managed");
config.setProperty(AvailableSettings.USE_GET_GENERATED_KEYS, "true");
config.setProperty(AvailableSettings.GENERATE_STATISTICS, "true");
config.setProperty(AvailableSettings.USE_REFLECTION_OPTIMIZER, "true");
config.setProperty(AvailableSettings.ORDER_UPDATES, "true");
config.setProperty(AvailableSettings.ORDER_INSERTS, "true");
config.setProperty(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true");
config.setProperty("jadira.usertype.autoRegisterUserTypes", "true");
entityClasses.forEach(config::addAnnotatedClass);
properties.entrySet().forEach(e -> config.setProperty(e.getKey(), e.getValue()));
return new DAOTestRule(config.buildSessionFactory());
}
}
/**
* Creates a new builder for {@link DAOTestRule}, which allows to customize a {@link SessionFactory}
* by different parameters. By default uses the H2 database in the memory mode.
*
* @return a new {@link Builder}
*/
public static Builder newBuilder() {
return new Builder();
}
private final SessionFactory sessionFactory;
/**
* Use {@link DAOTestRule#newBuilder()}
*/
private DAOTestRule(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
protected void before() throws Throwable {
if (ManagedSessionContext.hasBind(sessionFactory)) {
return;
}
final Session session = sessionFactory.openSession();
ManagedSessionContext.bind(session);
}
@Override
protected void after() {
if (!ManagedSessionContext.hasBind(sessionFactory)) {
return;
}
final Session currentSession = sessionFactory.getCurrentSession();
if (currentSession.isOpen()) {
currentSession.close();
}
ManagedSessionContext.unbind(sessionFactory);
}
/**
* Returns the current active session factory for injecting to DAOs.
*
* @return {@link SessionFactory} with an open session.
*/
public SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* Performs a call in a transaction
*
* @param call the call
* @param <T> the type of the returned result
* @return the result of the call
*/
public <T> T inTransaction(Callable<T> call) {
final Session session = sessionFactory.getCurrentSession();
final Transaction transaction = session.beginTransaction();
try {
final T result = call.call();
transaction.commit();
return result;
} catch (final Exception e) {
transaction.rollback();
throw Throwables.propagate(e);
}
}
/**
* Performs an action in a transaction
*
* @param action the action
*/
public void inTransaction(Runnable action) {
inTransaction(() -> {
action.run();
return true;
});
}
}
@@ -0,0 +1,33 @@
package io.dropwizard.testing.junit;
import org.hibernate.Session;
import org.junit.Rule;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class DAOTestRuleConfigTest {
@Rule
public final DAOTestRule database = DAOTestRule.newBuilder()
.setUrl("jdbc:h2:mem:rule-config-test")
.setDriver(org.h2.Driver.class)
.setUsername("username")
.useSqlComments(true)
.setHbm2DdlAuto("create")
.setShowSql(true)
.addEntityClass(TestEntity.class)
.setProperty("hibernate.format_sql", "true")
.build();
@Test
public void explicitConfigCreatesSessionFactory() {
// it yields a valid SessionFactory instance
assertThat(database.getSessionFactory()).isNotNull();
final Session currentSession = database.getSessionFactory().getCurrentSession();
// an instance of an entity contained in the package can be saved
currentSession.saveOrUpdate(new TestEntity("foo"));
}
}
Oops, something went wrong.

0 comments on commit ec7b632

Please sign in to comment.