Enterprise Java

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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button