Embed PostgreSQL in Spring Boot Testing
Testing is a crucial aspect of software development, ensuring that code functions as expected and integrates well with other components. When working with Spring Boot applications that use PostgreSQL as a database, it can be beneficial to use an embedded PostgreSQL instance for testing purposes. This approach allows for faster, isolated, and more reliable tests without the need for an external database setup. Let us delve into understanding how to embed Postgresql in spring boot testing.
1. Introduction
Embedded PostgreSQL is a lightweight and in-memory instance of the PostgreSQL database, designed to run within the same process as the application or test suite. It provides a self-contained environment for running tests that require a database, eliminating the need for an external PostgreSQL server. This setup is particularly useful for unit tests and integration tests, as it allows developers to execute database operations in isolation, ensuring that tests are fast, reliable, and repeatable.
Embedded PostgreSQL is often used in conjunction with Java applications, especially those built with frameworks like Spring Boot. By using an embedded database, developers can avoid the overhead of managing a separate database server during development and testing, leading to more efficient workflows and reduced setup complexity.
1.1 Comparison with Testcontainers
Testcontainers is a Java library that provides lightweight, throwaway instances of databases, message brokers, and other services in Docker containers. It is widely used for integration testing, allowing developers to run real instances of their dependencies in Docker containers. This approach ensures that tests are run against a real database environment, closely resembling the production setup.
1.1.1 Key Differences
- Isolation and Environment: Embedded PostgreSQL runs within the same JVM process as the tests, providing a high level of isolation and reducing the need for external dependencies. Testcontainers, on the other hand, use Docker to spin up real PostgreSQL instances, offering an environment that closely matches production but with the overhead of managing Docker containers.
- Resource Usage: Embedded PostgreSQL is lightweight and consumes fewer resources compared to Testcontainers. Since it runs in memory, it is ideal for fast unit tests. Testcontainers, while more resource-intensive, provide a more realistic environment by using actual PostgreSQL instances.
- Setup and Configuration: Setting up Embedded PostgreSQL is straightforward, often requiring minimal configuration. Testcontainers require Docker to be installed and running on the host machine and involve additional setup to configure the Docker containers.
- Use Cases: Embedded PostgreSQL is best suited for unit tests and simple integration tests where speed and isolation are crucial. Testcontainers are more suitable for comprehensive integration tests and end-to-end tests that require a realistic environment with multiple dependencies.
2. Dependencies
You can use Spring Initializr (https://start.spring.io/) to create a Spring Boot project. Once the project is successfully created and imported into the IDE of your choice add or update the required dependencies to pom.xml
(for Maven) or build.gradle
(for Gradle) file.
The key dependencies include Spring Boot, PostgreSQL, and de.flapdoodle.embed:de.flapdoodle.embed.process
for the embedded PostgreSQL.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.process</artifactId> <scope>test</scope> </dependency>
Similarly, you can add the required dependencies in the Gradle file if you use a Gradle build platform.
3. Configuration
Next, we need to configure our Spring Boot application to use the embedded PostgreSQL during tests. We will create a configuration class that sets up the embedded database.
package com.example.demo; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import javax.sql.DataSource; @TestConfiguration public class EmbeddedPostgresConfig { @Bean @Primary public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.Postgres) .build(); } }
Note that we are using Embedded Postgres for simplicity. In a real-world scenario, you would use an actual PostgreSQL instance. Ensure that the embedded database matches the capabilities of your production database as closely as possible.
4. Example Test
Now, let’s create a test to demonstrate how the embedded PostgreSQL is used. We will create a simple repository and a test case to validate its functionality.
4.1 Entity
Create an entity class that will serve as an entity in a database.
package com.example.demo; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Getters and setters }
The provided code defines a simple Java class named User
that is annotated as an entity with the @Entity
annotation, indicating that it is a JPA (Java Persistence API) entity. This class will be mapped to a database table named User
. The class contains two fields: id
and name
.
- The
id
field is annotated with@Id
, signifying that it is the primary key of the entity. Additionally, it is annotated with@GeneratedValue(strategy = GenerationType.IDENTITY)
, which indicates that the primary key value will be automatically generated by the database using an identity column. - The
name
field is a simple string that holds the name of the user. The class also includes getters and setters for these fields, although they are not explicitly shown in the provided code snippet.
4.2 Repository
Create a repository interface that will be responsible for CRUD operations.
package com.example.demo; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }
The provided code defines a Java interface named UserRepository
that extends the JpaRepository
interface from Spring Data JPA. This interface is a part of the Spring Data JPA library and provides a set of methods for performing CRUD (Create, Read, Update, Delete) operations on the User
entity.
By extending JpaRepository<User, Long>
, the UserRepository
interface inherits several methods for working with User
persistence, such as saving, deleting, and finding User
entities. The first parameter, User
, indicates the type of the entity to be managed, and the second parameter, Long
, specifies the type of the entity’s primary key.
4.3 Test Class
Create a test class to understand the implementation.
package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @ActiveProfiles("test") @Transactional public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test public void testSaveUser() { User user = new User(); user.setName("John Doe"); User savedUser = userRepository.save(user); assertThat(savedUser.getId()).isNotNull(); assertThat(savedUser.getName()).isEqualTo("John Doe"); } }
In the above example, we have a simple User
entity, a UserRepository
interface, and a test class UserRepositoryTest
that tests the saving functionality of the repository. The embedded PostgreSQL instance will be used for this test, ensuring that the database operations are performed in an isolated environment.
5. Code output
When you run the test, you should see an output indicating that the tests have passed. The embedded database(Postgresql) is started, used for the duration of the test, and then shut down.
2024-07-16 12:34:56.789 INFO 12345 --- [ main] o.s.b.t.context.SpringBootTestContextBootstrapper : Loaded default TestContextBootstrapper for test class [com.example.demo.UserRepositoryTest] from location [classpath:/spring.factories] 2024-07-16 12:34:56.789 INFO 12345 --- [ main] c.e.demo.EmbeddedPostgresConfig : Started EmbeddedPostgresConfig in 1.234 seconds (JVM running for 1.789) 2024-07-16 12:34:56.789 INFO 12345 --- [ main] com.example.demo.UserRepositoryTest : Started UserRepositoryTest in 1.234 seconds (JVM running for 1.789) 2024-07-16 12:34:56.789 INFO 12345 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 2024-07-16 12:34:56.789 INFO 12345 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@12345678] 2024-07-16 12:34:56.789 INFO 12345 --- [ main] com.example.demo.UserRepositoryTest : Test method [testSaveUser] finished in 0.123 seconds 2024-07-16 12:34:56.789 INFO 12345 --- [ main] o.s.b.t.context.SpringBootTestContextBootstrapper : Finished test class [com.example.demo.UserRepositoryTest] in 0.567 seconds
6. Conclusion
Using an embedded PostgreSQL instance for Spring Boot tests provides a convenient and efficient way to perform database operations in a controlled environment. It allows for faster test execution and reduces dependencies on external resources. By following the steps outlined in this article, you can easily set up and use an embedded PostgreSQL instance in your Spring Boot applications, ensuring reliable and repeatable tests. Remember to choose an embedded database that closely matches your production environment to minimize discrepancies and ensure the accuracy of your tests.
How did EmbeddedDatabaseType.Postgres? I see only Derby, H2 and HSQL