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

  • 7 Microservices Best Practices for Developers
  • Spring Cloud Stream: A Brief Guide
  • Keep Your Application Secrets Secret
  • Manage Microservices With Docker Compose

Trending

  • How to Build Local LLM RAG Apps With Ollama, DeepSeek-R1, and SingleStore
  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Operational Principles, Architecture, Benefits, and Limitations of Artificial Intelligence Large Language Models
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Testing Spring Boot Integration With Vault and Postgres Using Testcontainers Framework

Testing Spring Boot Integration With Vault and Postgres Using Testcontainers Framework

Need to write integration tests for your application against a popular third-party solution such as a database?

By 
Piotr Mińkowski user avatar
Piotr Mińkowski
·
Feb. 01, 19 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
33.5K Views

Join the DZone community and get the full member experience.

Join For Free

I have already written many articles where I was using Docker containers for running some third-party solutions integrated with my sample applications. Building integration tests for such applications may not be an easy task without Docker containers. Especially if our application integrates with databases, message brokers, or some other popular tools. If you are planning to build such integration tests, you should definitely take a look on Testcontainers.

Testcontainers is a Java library that supports JUnit tests, providing a fast and lightweight way for running instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. It provides modules for the most popular relational and NoSQL databases like Postgres, MySQL, Cassandra, or Neo4j. It also allows running popular products like Elasticsearch, Kafka, Nginx, or HashiCorp's Vault. Today, I'm going to show you a more advanced sample of JUnit tests that use Testcontainers to check out an integration between Spring Boot/Spring Cloud application, Postgres database, and Vault.

For the purposes of that example, we will use the case described in one of my previous articles Secure Spring Cloud Microservices with Vault and Nomad. Let us recall that use case.

I described there how to use a very interesting Vault feature called secret engines for generating database user credentials dynamically. I used Spring Cloud Vault module in my Spring Boot application to automatically integrate with that feature of Vault. The implemented mechanism is pretty easy. The application calls Vault secret engine before it tries to connect to Postgres database on startup. Vault is integrated with Postgres via secret engine, and that's why it creates a user with sufficient privileges on Postgres. Then, generated credentials are automatically injected into auto-configured Spring Boot properties used for connecting with database spring.datasource.username and spring.datasource.password. The following picture illustrates the described solution:

Ok, we know how it works, now the question is how to automatically test it. With Testcontainers, it is possible with just a few lines of code.

1. Building the Application

Let's begin with a short intro to the application code; it is very simple. Here's the list of dependencies required for building an application that exposes REST API and integrates with Postgres and Vault.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>

The application connects to Postgres, enables integration with Vault via Spring Cloud Vault, and automatically creates/updates tables on startup.

spring:
  application:
    name: callme-service
  cloud:
    vault:
      uri: http://192.168.99.100:8200
      token: ${VAULT_TOKEN}
      postgresql:
        enabled: true
        role: default
        backend: database
  datasource:
    url: jdbc:postgresql://192.168.99.100:5432/postgres
  jpa.hibernate.ddl-auto: update

It exposes the single endpoint. The following method is responsible for handling incoming requests. It just inserts a record to the database and returns a response with the app name, version, and ID of the inserted record.

@RestController
@RequestMapping("/callme")
public class CallmeController {

    private static final Logger LOGGER = LoggerFactory.getLogger(CallmeController.class);

    @Autowired
    Optional<BuildProperties> buildProperties;
    @Autowired
    CallmeRepository repository;

    @GetMapping("/message/{message}")
    public String ping(@PathVariable("message") String message) {
        Callme c = repository.save(new Callme(message, new Date()));
        if (buildProperties.isPresent()) {
            BuildProperties infoProperties = buildProperties.get();
            LOGGER.info("Ping: name={}, version={}", infoProperties.getName(), infoProperties.getVersion());
            return infoProperties.getName() + ":" + infoProperties.getVersion() + ":" + c.getId();
        } else {
            return "callme-service:"  + c.getId();
        }
    }

}

2. Enabling Testcontainers

To enable Testcontainers for our project, we need to include some dependencies to our Maven pom.xml. We have dedicated modules for Postgres and Vault. We also include a Spring Boot Test dependency because we would like to test the whole Spring Boot app.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>vault</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>

3. Running Vault Test Container

Testcontainers framework supports JUnit 4/JUnit 5 and Spock. The Vault container can be started before tests if it is annotated with @Rule or @ClassRule. By default, it uses version 0.7, but we can override it with the newest version, which is 1.0.2. We also may set a root token, which is then required by Spring Cloud Vault for integration with Vault.

@ClassRule
public static VaultContainer vaultContainer = new VaultContainer<>("vault:1.0.2")
    .withVaultToken("123456")
    .withVaultPort(8200);

That root token can be overridden before starting the JUnit test on the test class.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
    "spring.cloud.vault.token=123456"
})
public class CallmeTest { ... }

4. Running Postgres Test Container

As an alternative to @ClassRule, we can manually start the container in a @BeforeClass or @Before method in the test. With this approach, you will also have to stop it manually in @AfterClass or @After method. We start Postgres container manually because by default, it is exposed on a dynamically generated port, which needs to be set for Spring Boot application before starting the test. The listen port is returned by method getFirstMappedPort invoked on PostgreSQLContainer.

private static PostgreSQLContainer postgresContainer = new PostgreSQLContainer()
    .withDatabaseName("postgres")
    .withUsername("postgres")
    .withPassword("postgres123");

@BeforeClass
public static void init() throws IOException, InterruptedException {
    postgresContainer.start();
    int port = postgresContainer.getFirstMappedPort();
    System.setProperty("spring.datasource.url", String.format("jdbc:postgresql://192.168.99.100:%d/postgres", postgresContainer.getFirstMappedPort()));
    // ...
}

@AfterClass
public static void shutdown() {
    postgresContainer.stop();
}

5. Integrating Vault and Postgres Containers

Once we have successfully started both Vault and Postgres containers, we need to integrate them via Vault secret engine. First, we need to enable the database secret engine Vault. After that, we must configure the connection to Postgres. The last step is to configure a role. A role is a logical name that maps to a policy used to generate those credentials. All these actions may be performed using Vault commands. You can launch a command on the Vault container using execInContainer method. Vault configuration commands should be executed just after Postgres container startup.

@BeforeClass
public static void init() throws IOException, InterruptedException {
    postgresContainer.start();
    int port = postgresContainer.getFirstMappedPort();
    System.setProperty("spring.datasource.url", String.format("jdbc:postgresql://192.168.99.100:%d/postgres", postgresContainer.getFirstMappedPort()));
    vaultContainer.execInContainer("vault", "secrets", "enable", "database");
    String url = String.format("connection_url=postgresql://{{username}}:{{password}}@192.168.99.100:%d?sslmode=disable", port);
    vaultContainer.execInContainer("vault", "write", "database/config/postgres", "plugin_name=postgresql-database-plugin", "allowed_roles=default", url, "username=postgres", "password=postgres123");
    vaultContainer.execInContainer("vault", "write", "database/roles/default", "db_name=postgres",
        "creation_statements=CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';GRANT SELECT, UPDATE, INSERT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";GRANT USAGE,  SELECT ON ALL SEQUENCES IN SCHEMA public TO \"{{name}}\";",
        "default_ttl=1h", "max_ttl=24h");
}

6. Running Application Tests

Finally, we may run application tests. We just call the single endpoint exposed by the app using TestRestTemplate, and verify the output.

@Autowired
TestRestTemplate template;

@Test
public void test() {
    String res = template.getForObject("/callme/message/{message}", String.class, "Test");
    Assert.assertNotNull(res);
    Assert.assertTrue(res.endsWith("1"));
}

If you are interested in what exactly happens during the test, you can set a breakpoint inside the test method and execute the docker ps command manually.

Spring Framework Spring Boot PostgreSQL Integration Database application Spring Cloud Docker (software) Testing

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • 7 Microservices Best Practices for Developers
  • Spring Cloud Stream: A Brief Guide
  • Keep Your Application Secrets Secret
  • Manage Microservices With Docker Compose

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!