Testcontainers is an open-source Java library that simplifies integration testing by providing lightweight, disposable containers for database systems, message brokers, and other third-party services. In this article we will learn how to combine Testcontainer with a JPA/Hibernate Test case.
TestContainer and Databases
Firstly, if you are new to TestContainers, we recommend checking this article for a quick overview of it: Getting Started with Testcontainers for Java
Testcontainers enables you to test against real instances of databases (MySQL, PostgreSQL, MongoDB, etc.) or other services (Kafka, Redis, Elasticsearch) rather than using in-memory or mocked versions. This ensures more accurate and realistic testing scenarios.
In order to test an application with TestContainer, make sure you have the Docker service up and running:
service docker start
Then, let’s see which are the key dependencies you need to add in a TestContainer application which run Tests against a database:
<!-- MySQL JDBC Driver --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.1.0</version> <scope>runtime</scope> </dependency> <!-- TestContainer core API --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <scope>test</scope> </dependency> <!-- MySQL TestContainer --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>mysql</artifactId> <scope>test</scope> </dependency>
As you can see, the key dependencies are:
- The JDBC Driver which you need for the JPA
- The core TestContainer dependency
- Finally, the TestContainer dependency for the Database you are going to Test
Coding the Test Class
Then, let’s code the real TestContainer Test Class:
@Testcontainers @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestContainerJPA { private EntityManagerFactory emf; @Container private static final MySQLContainer MYSQL_CONTAINER = (MySQLContainer) new MySQLContainer() .withDatabaseName("testdb") .withUsername("root") .withPassword("password") .withReuse(true); @BeforeAll void setup() { System.setProperty("db.port", MYSQL_CONTAINER.getFirstMappedPort().toString()); emf = Persistence.createEntityManagerFactory("my-persistence"); } @Test void contextLoads() { EntityManager entityManager = emf.createEntityManager(); entityManager.getTransaction().begin(); Customer c = new Customer(); c.setFirstName("John"); c.setLastName("Doe"); c.setEmail("[email protected]"); entityManager.persist(c); entityManager.getTransaction().commit(); List<Customer> customers = entityManager.createQuery("SELECT c FROM Customer c", Customer.class) .getResultList(); for (Customer customer : customers) { System.out.println(customers); } Assertions.assertEquals(1, customers.size()); } }
- The
@Testcontainers
annotation allows us to integrate Testcontainers with JUnit 5. @TestInstance(TestInstance.Lifecycle.PER_CLASS)
ensures that the container is shared across test methods within the class.@Container
creates a Container using the MySQL container instance.- In
setup()
, the mapped port of the MySQL container is set as a system property to configure the database connection URL. - Then, the
contextLoads()
method creates anEntityManager
, persists aCustomer
entity, and fetches all customers from the database using JPA.
Finally, the Model Class which we use to fetch and insert data is the following Entity:
@Entity public class Customer implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column private String email; @Column private String firstName; @Column private String lastName; // getters/setters omitted for brevity }
Adding the JPA Configuration
Finally, we will add the persistence.xml
file to define the Connection settings that JPA will use to create the Entity Manager:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1"> <persistence-unit name="my-persistence"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:${db.port}/testdb?createDatabaseIfNotExist=true"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="password"/> <property name ="hibernate.show_sql" value = "true" /> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> </properties> </persistence-unit> </persistence>
Notice that we need to use the same Database settings that we declare in the TestContainer Class.
Here is the Project view which shows where you need to place each file:
Testing our application
To Test our TestContainer Class simply run the Maven install goal and verify that the Customer Entity displays on the Console:
[INFO] Running com.mastertheboss.TestContainerJPA 2023-11-30_18:42:25.113 INFO org.testcontainers.images.PullPolicy - Image pull policy will be performed by: DefaultPullPolicy() 2023-11-30_18:42:25.117 INFO o.t.utility.ImageNameSubstitutor - Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor') 2023-11-30_18:42:25.274 INFO o.t.d.DockerClientProviderStrategy - Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first 2023-11-30_18:42:25.432 INFO o.t.d.DockerClientProviderStrategy - Found Docker environment with local Unix socket (unix:///var/run/docker.sock) 2023-11-30_18:42:25.434 INFO o.testcontainers.DockerClientFactory - Docker host IP address is localhost 2023-11-30_18:42:25.444 INFO o.testcontainers.DockerClientFactory - Connected to docker: Server Version: 20.10.22 API Version: 1.41 Operating System: Fedora Linux 35 (Workstation Edition) Total Memory: 63969 MB 2023-11-30_18:42:25.478 INFO tc.testcontainers/ryuk:0.5.1 - Creating container for image: testcontainers/ryuk:0.5.1 2023-11-30_18:42:25.625 INFO tc.testcontainers/ryuk:0.5.1 - Container testcontainers/ryuk:0.5.1 is starting: 74c290419ff42ea2c5ff7f876964da77811ffad8ffbcdae3085b1ca4e5d5. . . . . Hibernate: insert into Customer (email,firstName,lastName,removed) values (?,?,?,0) Hibernate: select c1_0.id,c1_0.email,c1_0.firstName,c1_0.lastName from Customer c1_0 where c1_0.removed=0 [Customer [id=1, [email protected], firstName=John, lastName=Doe]]
Conclusion
Using Testcontainers with JPA enables integration testing against a containerized database, ensuring your JPA entities, repositories, and database interactions work correctly.
Feel free to expand these tests to cover more scenarios, such as testing different CRUD operations, relationships between entities, and transactional behavior.
Source code: https://github.com/fmarchioni/mastertheboss/tree/master/test/testcontainer-jpa
Found the article helpful? if so please follow us on Socials