Testcontainers and Cloud-Native Testing: Kubernetes, AWS LocalStack

Illustration for Testcontainers and Cloud-Native Testing: Kubernetes, AWS LocalStack
By Last updated:

Modern software development is increasingly cloud-native, with applications deployed on Kubernetes clusters or built on top of AWS services. Testing these systems poses unique challenges: how do you simulate AWS S3, DynamoDB, or Kubernetes services reliably in your local or CI/CD environment? This is where Testcontainers comes in. By combining Docker-based ephemeral containers with providers like LocalStack and Kubernetes integration, developers can test against real dependencies without mocking away the cloud.

In this tutorial, we’ll dive deep into cloud-native testing with Testcontainers, focusing on Kubernetes integration and AWS LocalStack. You’ll learn how to set up, configure, and run tests that reflect production-like environments, ensuring your applications behave reliably before deployment.


What is Testcontainers?

Testcontainers is a Java library that provides lightweight, disposable containers for integration testing. It works with popular databases, message brokers, web servers, and cloud services. Instead of relying on mocks, you run your tests against real services inside Docker containers, making tests more realistic.

Key Benefits:

  • Run tests against real cloud APIs (via LocalStack).
  • Simulate Kubernetes deployments.
  • Ensure consistency between local dev and CI/CD pipelines.
  • Avoid flaky integration tests caused by mismatched environments.

Why Cloud-Native Testing Matters

When working with microservices and cloud-native apps, bugs often arise from:

  • Misconfigured Kubernetes manifests.
  • Incorrect AWS IAM permissions.
  • Inconsistent environments between local dev and production clusters.

By testing with Testcontainers + LocalStack + Kubernetes, you can:

  • Validate manifests before deploying to production.
  • Ensure AWS API interactions work as expected.
  • Catch infrastructure issues early in the CI/CD pipeline.

Using Testcontainers with AWS LocalStack

1. Adding Dependencies

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>localstack</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>cloud.localstack</groupId>
    <artifactId>localstack-utils</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

2. Writing a Test with LocalStack (S3 Example)

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import static org.assertj.core.api.Assertions.assertThat;

public class S3LocalStackTest {

    private static final LocalStackContainer localstack = new LocalStackContainer(
            DockerImageName.parse("localstack/localstack:1.4.0"))
            .withServices(LocalStackContainer.Service.S3);

    static {
        localstack.start();
    }

    @Test
    void testS3BucketCreation() {
        S3Client s3 = S3Client.builder()
                .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
                .region(localstack.getRegion())
                .credentialsProvider(localstack.getDefaultCredentialsProvider())
                .build();

        String bucketName = "test-bucket";
        s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());

        ListBucketsResponse buckets = s3.listBuckets();
        assertThat(buckets.buckets()).extracting(Bucket::name).contains(bucketName);
    }
}

Explanation:

  • Starts LocalStack with S3 enabled.
  • Creates and verifies a bucket.
  • Mimics real AWS interactions but runs locally.

Testing Kubernetes Workloads with Testcontainers

Testcontainers supports running Kubernetes clusters (via KinD or K3s) inside containers for testing.

Example: Deploying a Pod Manifest

import org.junit.jupiter.api.Test;
import org.testcontainers.k3s.K3sContainer;

import java.io.File;

public class KubernetesTest {

    @Test
    void testKubernetesDeployment() throws Exception {
        try (K3sContainer k3s = new K3sContainer()) {
            k3s.start();
            String kubeConfigYaml = k3s.getKubeConfigYaml();
            System.out.println("KubeConfig: " + kubeConfigYaml);

            // Example: Apply manifests using kubectl
            Process process = new ProcessBuilder("kubectl", "--kubeconfig=-", "apply", "-f",
                    new File("src/test/resources/deployment.yaml").getAbsolutePath())
                    .inheritIO()
                    .start();
            process.waitFor();
        }
    }
}

Explanation:

  • Spins up a K3s Kubernetes cluster.
  • Applies a manifest file (e.g., Deployment, Service).
  • Ensures manifests work before hitting production clusters.

CI/CD Integration: Jenkins & GitHub Actions

Cloud-native tests need to run in CI/CD pipelines. Testcontainers integrates smoothly:

  • Jenkinsfile example:
pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh './mvnw test'
            }
        }
    }
}
  • GitHub Actions example:
name: Java CI with Testcontainers

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      docker:
        image: docker:dind
        privileged: true
    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Run Tests
        run: ./mvnw test

Best Practices

  • Use LocalStack for AWS APIs instead of mocks.
  • Keep manifests under version control and validate them with Testcontainers.
  • Leverage reusable containers in CI/CD to speed up builds.
  • Combine with Mockito for unit-level isolation and Testcontainers for integration.

Version Tracker

  • JUnit 4 → JUnit 5: Native Jupiter support for extensions.
  • Mockito Updates: Static/final method mocking support.
  • Testcontainers: Growing ecosystem (AWS, Kubernetes, databases, message brokers).

Conclusion

Testcontainers with LocalStack and Kubernetes bridges the gap between local testing and cloud production environments. Instead of relying on mocks, you validate your application against realistic, containerized services. This reduces flakiness, boosts confidence, and ensures that CI/CD pipelines catch bugs before production.


Key Takeaways

  • Testcontainers enables cloud-native testing with LocalStack and Kubernetes.
  • Run AWS API calls locally without real cloud costs.
  • Validate Kubernetes manifests before deploying.
  • Integrate easily with CI/CD pipelines like Jenkins and GitHub Actions.
  • Combine with Mockito & JUnit 5 for full-spectrum testing.

FAQ

1. What’s the difference between unit and integration tests?
Unit tests isolate small code pieces, while integration tests validate interactions with real dependencies like DBs or AWS.

2. How do I mock a static method in Mockito?
Use mockStatic() introduced in Mockito 3.4+.

3. How can Testcontainers help in CI/CD pipelines?
It spins up disposable dependencies (DBs, queues, LocalStack) so tests mimic production in CI/CD.

4. What is the difference between TDD and BDD?
TDD focuses on unit test-driven design; BDD emphasizes behavior using domain language.

5. How do I fix flaky tests in Java?
Use Testcontainers' reusable containers, wait strategies, and explicit assertions.

6. Can I run Testcontainers without Docker?
No, Docker is required, but Podman support is being explored.

7. Is LocalStack free?
LocalStack has both free and pro tiers; free covers common AWS services like S3, SQS, DynamoDB.

8. Can I test Kubernetes CRDs with Testcontainers?
Yes, using K3s/KinD containers, you can apply CRDs and test them locally.

9. How does Testcontainers compare to mocks?
Mocks test behavior; Testcontainers validates real integration scenarios.

10. Can I run Testcontainers in parallel?
Yes, with proper resource limits and reusable container strategies.