Testcontainers for Java

repository·main·Indexed 11 days ago

https://github.com/testcontainers/testcontainers-java

A Java library that enables developers to run lightweight, throwaway instances of common databases, Selenium web browsers, or any other Docker-compatible software during JUnit tests to ensure a consistent, isolated environment for integration testing.

Tokens
60.7K
Snippets
241
Records
310
Agent score
93%

What's inside Testcontainers

  1. Overview of Testcontainers for Java

    main
    Testcontainers is a Java library designed to support JUnit tests by providing lightweight, throwaway instances of common dependencies. It allows you to run databases, Selenium web browsers, or any other software that can be packaged in a Docker container directly from your tests. This ensures a consistent, isolated environment for integration testing.
  2. Use Testcontainers for database testing instead of H2 or local databases

    main

    Testcontainers provides database support for DAO unit tests and end-to-end integration tests. Use it in the following scenarios:

    • Replacing H2 for feature-dependent tests: When your DAO tests require specific database features that H2 does not emulate, Testcontainers provides 100% compatibility by running the actual database inside a container.
    • Replacing local or VM-based databases: When tests require a database to be present, Testcontainers ensures the database starts in a known, clean state, preventing contamination between test runs or differences between developer environments.

    Note that while Testcontainers offers higher fidelity than H2, it is less performant. It is recommended to use mocks for higher-level components and limit the number of tests that interact directly with the database.

  3. Use Testcontainers with external frameworks

    main

    Testcontainers provides direct integrations with several open-source testing frameworks. Instead of manually managing container lifecycles within your test code, you can use these specialized extensions to integrate container management directly into the framework's lifecycle (e.g., as part of property-based testing or specialized test runners).

    Supported integrations include:

    • jqwik: Use jqwik-testcontainers for property-based testing integration.
    • Kotest: Use kotest-extensions-testcontainers for Kotest-specific extensions.
    • Synthesized: Use TDK-Testcontainers integration for Synthesized data workflows.
    • TCI: Use the Testcontainers Infrastructure (TCI) Framework for infrastructure-level integration.
  4. How the Testcontainers JUnit 5 extension works

    main

    The JUnit 5 integration uses the @Testcontainers annotation on a test class to enable the extension. The extension scans for fields annotated with @Container and manages their lifecycle automatically by calling their Startable interface methods.

    Container Lifecycles

    • Restarted Containers: If a container is declared as an instance field, it will be started before every test method and stopped after every test method.
    • Shared Containers: If a container is declared as a static field, it will be shared between all test methods in the class. It starts once before the first test method and stops after the last test method has executed.

    Important Limitations

    • Parallel Execution: This extension is only tested with sequential test execution. Using it with parallel test execution is unsupported and may cause unintended side effects.
    • Nested Classes: Shared containers (static fields) cannot be declared inside @Nested test classes because nested classes must be non-static and cannot contain static fields.
    @Testcontainers
    class MyTest {
    
        @Container
        static GenericContainer<?> sharedContainer = new GenericContainer<>("redis:latest"); // Shared across all methods
    
        @Container
        GenericContainer<?> restartedContainer = new GenericContainer<>("postgres:latest"); // Restarted for every method
    
        @Test
        void testOne() {
            // ...
        }
    }
  5. Create images on-the-fly using ImageFromDockerfile

    main

    When no pre-existing Docker image is available, or when you need to customize a base image for specific tests, you can use ImageFromDockerfile to build a temporary image from a Dockerfile.

    To use it, pass an instance of ImageFromDockerfile as a constructor parameter to GenericContainer. Testcontainers will execute a docker build to create the image before starting the container.

    GenericContainer container = new GenericContainer(
        new ImageFromDockerfile()
            // configuration here
    );
  6. Decide if a new module is necessary

    main

    Before creating a new module, evaluate if it provides enough value to be maintained as a first-class Testcontainers module. A new module is appropriate if it meets these criteria:

    • Popularity: It enables use of a popular or rapidly growing technology.
    • Value Add: It goes beyond a simple GenericContainer snippet by:
      • Encapsulating complex container startup problems.
      • Providing technology-specific wait strategies.
      • Enabling straightforward usage of specific client libraries.

    If the contribution does not meet these, consider publishing a code snippet, contributing an example to the repository, or publishing your own third-party library instead.

  7. Understand Testcontainers Shaded Dependencies

    main

    To prevent classpath conflicts with your application or test code, Testcontainers 'shades' (relocates) certain implementation-detail dependencies.

    • Public API Dependencies: Libraries like JUnit, docker-java-{api,transport}, JNA, and visible-assertions are part of the public API and are NOT shaded.
    • Shaded Dependencies: Implementation details such as docker-java-core, Guava, and OkHttp are shaded and relocated under the org.testcontainers.shaded package to avoid version conflicts.