DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Boot - Microservice- JaxRS Jersey - HATEOAS - JerseyTest - Integration
  • Integrating Jenkins With Playwright TypeScript: A Complete Guide
  • Supercharging Pytest: Integration With External Tools
  • Mocking and Its Importance in Integration and E2E Testing

Trending

  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • Agentic AI for Automated Application Security and Vulnerability Management
  • Ethical AI in Agile
  1. DZone
  2. Data Engineering
  3. Databases
  4. Database Integration Tests With Spring Boot and Testcontainers

Database Integration Tests With Spring Boot and Testcontainers

In this tutorial, we'll show you how to use Testcontainers for integration testing with Spring Data JPA and a PostgreSQL database.

By 
Andrei Rogalenko user avatar
Andrei Rogalenko
·
May. 31, 23 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
9.6K Views

Join the DZone community and get the full member experience.

Join For Free

With Spring Data JPA, you can easily create database queries and test them with an embedded H2 database.

But sometimes, testing against a real database is much more useful, especially if we use queries tied to a specific database implementation.

In this tutorial, we'll show you how to use Testcontainers for integration testing with Spring Data JPA and a PostgreSQL database.

We will be testing JPQL and native SQL queries created using the @Query annotation in Spring Data JPA.

Configuration

In order to use a PostgreSQL database in our tests, we must add the test-only Testcontainers dependency and the PostgreSQL driver to our pom.xml file:

XML
 
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.10.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>


We will also create an application.properties file in the testing resources directory, in which we will tell Spring to use the desired driver class, as well as create and delete the database schema each time the test is run:

YAML
 
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create-drop


Unit Test

To start using a PostgreSQL instance in a single test class, you need to create a container definition and then use its parameters to establish a connection:

Java
 
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class})
public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1")
      .withDatabaseName("integration-tests-db")
      .withUsername("sa")
      .withPassword("sa");

    static class Initializer
      implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
              "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
              "spring.datasource.username=" + postgreSQLContainer.getUsername(),
              "spring.datasource.password=" + postgreSQLContainer.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}


In the example above, we used @ClassRule from JUnit to set up the database container before executing the test methods. We also created a static inner class that implements the ApplicationContextInitializer. Finally, we have applied the @ContextConfiguration annotation to our test class with the initialization class as a parameter.

After completing these three steps, we can set the connection parameters before publishing the Spring context.

Now we use two UPDATE queries:

Java
 
@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
  @Param("name") String name);

@Modifying
@Query(value = "UPDATE Users u SET u.status = ? WHERE u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);


And test in the configured runtime environment:

Java
 
@Test
@Transactional
public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){
    insertUsers();
    int updatedUsersSize = userRepository.updateUserSetStatusForName(0, "SAMPLE");
    assertThat(updatedUsersSize).isEqualTo(2);
}

@Test
@Transactional
public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){
    insertUsers();
    int updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, "SAMPLE");
    assertThat(updatedUsersSize).isEqualTo(2);
}

private void insertUsers() {
    userRepository.save(new User("SAMPLE", "email@example.com", 1));
    userRepository.save(new User("SAMPLE1", "email2@example.com", 1));
    userRepository.save(new User("SAMPLE", "email3@example.com", 1));
    userRepository.save(new User("SAMPLE3", "email4@example.com", 1));
    userRepository.flush();
}


In the script above, the first test succeeds, and the second throws an InvalidDataAccessResourceUsageException with the message:

Shell
 
Caused by: org.postgresql.util.PSQLException: ERROR: column "u" of relation "users" does not exist


If we were to run the same tests using the embedded H2 database, both would succeed, but PostgreSQL does not accept aliases in a SET statement. We can quickly fix the request by removing the problematic alias:

Java
 
@Modifying
@Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);


This time both tests passed successfully. In this example, we've used Testcontainers to detect a problem with a native query that would otherwise only be discovered after migrating to a production database. It should also be noted that using JPQL queries is generally safer because Spring translates them correctly depending on the database provider used.

Shared Database Instance

In the previous section, we described how to use Testcontainers in a unit test. In real cases, I would like to use the same database container in several tests due to the relatively long startup time.

Let's create a generic class for creating a database container by inheriting PostgreSQLContainer and overriding the start() and stop() methods:

Java
 
public class BaeldungPostgresqlContainer extends PostgreSQLContainer<BaeldungPostgresqlContainer> {
    private static final String IMAGE_VERSION = "postgres:11.1";
    private static BaeldungPostgresqlContainer container;

    private BaeldungPostgresqlContainer() {
        super(IMAGE_VERSION);
    }

    public static BaeldungPostgresqlContainer getInstance() {
        if (container == null) {
            container = new BaeldungPostgresqlContainer();
        }
        return container;
    }

    @Override
    public void start() {
        super.start();
        System.setProperty("DB_URL", container.getJdbcUrl());
        System.setProperty("DB_USERNAME", container.getUsername());
        System.setProperty("DB_PASSWORD", container.getPassword());
    }

    @Override
    public void stop() {
        //do nothing, JVM handles shut down
    }
}


Leaving the stop() method empty allows the JVM to handle the termination of the container itself. 

We'll also implement a simple singleton where only the first test starts the container, and each subsequent test uses an existing instance. 

In the start() method, we use System#setProperty to store the connection settings in environment variables.

Now we can write them to the application.properties file:

YAML
 
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}


Now we use our utility class in the test definition:

Java
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTCAutoIntegrationTest {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance();

    // tests
}


As in the previous examples, we have applied the @ClassRule annotation to the field with the container definition. This way, the DataSource connection parameters are populated with the correct values before the Spring context is created.

We can now implement multiple tests using the same database instance by simply defining a @ClassRule annotated field created with our BaeldungPostgresqlContainer utility class.

Conclusion

In this article, we have shown testing methods on a production database using Testcontainers.

We also looked at examples of using a unit test using the ApplicationContextInitializer mechanism from Spring, as well as class implementations for reusing a database instance.

We also showed how Testcontainers can help identify compatibility issues between multiple database vendors, especially for native queries.

Spring Data unit test PostgreSQL Integration Testing

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot - Microservice- JaxRS Jersey - HATEOAS - JerseyTest - Integration
  • Integrating Jenkins With Playwright TypeScript: A Complete Guide
  • Supercharging Pytest: Integration With External Tools
  • Mocking and Its Importance in Integration and E2E Testing

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!