New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DAOTestRule backport #1905

Merged
merged 1 commit into from Jan 30, 2017
Jump to file or symbol
Failed to load files and symbols.
+494 −1
Diff settings

Always

Just for now

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
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.
ProTip! Use n and p to navigate between commits in a pull request.