DAO integration test
Use this demo code as a guideline for how to setup DAO integration tests with test containers. Notice that the integration test is in the “final-with-test” branch of the repository. If you wish to follow this tutorial, then clone the main branch and go from there.
The project is setup in accordance with the JPA setup guidelines and the example is based on this JPA CodeLab exercise.
You are welcome to follow this guided video tutorial - which contains a run-through of what comes below:
The integration tests relies on a number of dependencies:
- JUnit 5 (org.junit.jupiter)
- Hamcrest (org.hamcrest)
- Testcontainers (org.testcontainers)
- Logging (ch.qos.logback and org.slf4j)
Copy the dependencies from this version of the pom.xml. Remember to also get the version properties:
<testcontainers.version>1.20.4</testcontainers.version>
<logback-classic>1.5.16</logback-classic>
<org.slf4j>2.0.16</org.slf4j>
<hamcrest>3.0</hamcrest>
Newer versions might be available at Maven Central. Check them out, but make sure the versions you pick are stable.
The easiest way to do this in IntelliJ is to choose “Generate…” in the DAO class - and then choose “Test…”.
- EntityManagerFactory
- DolphinDAO
- Testdata (p1, p2, p3)
Example:
private EntityManagerFactory emf;
private DolphinDAO dolphinDAO;
private Person p1, p2, p3;
private List<Person> persons;
@BeforeAll
void initOnce() {
emf = HibernateConfig.getEntityManagerFactoryForTest();
dolphinDAO = new DolphinDAO(emf);
}
- This method is executed before each individual unit test.
- So delete all tables
- Reset autogenerated primary keys
- Populate the tables with known data
This is an example:
@BeforeEach
void setUp() {
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
em.createNativeQuery("TRUNCATE TABLE fee, person_detail, person RESTART IDENTITY CASCADE")
.executeUpdate();
em.getTransaction().commit();
}
catch (Exception e) {
throw new RuntimeException("Failed to truncate tables", e);
}
persons = PersonPopulator.populatePersons(dolphinDAO);
if (persons.size() == 3) {
p1 = persons.get(0);
p2 = persons.get(1);
p3 = persons.get(2);
} else {
throw new ApiException(500, "Populator doesnt work");
}
}
You can find an example of a Populator class here. This trick here is to be able to get a handle on the entities you persist. One way is to store the entities after persisting them into an arraylist, and then return the array to the test class. By having references to the entities in the test database, you can easily pick out the ids later.
In this example, we will declare some variables in the test class:
private Person p1, p2, p3;
private List<Person> persons;
In the @BeforeEach section, we will call a populator method that return an array of entities and then assign the s1
and s2
to the persisted test entities:
persons = PersonPopulator.populatePersons(dolphinDAO);
p1 = persons.get(0);
p2 = persons.get(1);
p3 = persons.get(2);
Make sure the HibernateConfig
file is updated with right test properties:
private static Properties setTestProperties(Properties props) {
props.put("hibernate.connection.driver_class", "org.testcontainers.jdbc.ContainerDatabaseDriver");
props.put("hibernate.connection.url", "jdbc:tc:postgresql:16.2:///test_db");
props.put("hibernate.archive.autodetection", "hbm,class");
props.put("hibernate.show_sql", "false");
props.put("hibernate.hbm2ddl.auto", "create-drop");
return props;
}
You don’t need this step, but spinning up the test container generates a lot of verbose log info by default. If you configure a logger, you can limit the noise:
Create a
resource
folder in the test folder. This is how the folders can be structured:Add a file called “logback-test.xml” with the content:
<configuration> <!-- Suppress debug logs from Testcontainers --> <logger name="org.testcontainers" level="INFO"/> <logger name="org.testcontainers.utility" level="WARN"/> <logger name="docker" level="WARN"/> <!-- Optional: Reduce Hibernate verbosity --> <logger name="org.hibernate.SQL" level="WARN"/> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="WARN"/> <logger name="org.hibernate.engine.jdbc.batch.internal.BatchingBatch" level="ERROR"/> <!-- Console Appender --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
Craft your tests of each DAO method, one by one. Make sure that you cover most cases:
First test that the number of rows in the tables match
Secondly, make sure that the entities are the right ones, and that the attributes have the expected content. You might need to create
equals
andhashcode
methods on the entities to help the junit / hamcrest matchers to work correctly. Hamcrest offers the matchersamePropertyValuesAs
that can help you getting the job done like this:assertThat(p1ToUpdate, samePropertyValuesAs(p1));