pytest-docker

repository·master·Indexed 19 days ago

https://github.com/avast/pytest-docker

A pytest plugin that provides fixtures to simplify integration testing with Docker and Docker Compose. It automates the container lifecycle, allowing developers to define environments in YAML and interact with them via fixtures such as docker_ip and docker_services. Features include dynamic port mapping via port_for, service readiness checks with wait_until_responsive, and configurable container reuse scopes via the --container-scope CLI flag.

Tokens
2.6K
Snippets
11
Records
13
Agent score
66%

What's inside pytest-docker

  1. Use pytest-docker for integration tests

    master

    To use pytest-docker, define your services in a docker-compose.yml file. The plugin will automatically spin up these containers for the duration of your tests.

    Example workflow:

    1. Define services in docker-compose.yml.
    2. Use docker_ip and docker_services fixtures in your tests to interact with containers.
    3. Use docker_services.port_for(service_name, container_port) to resolve the host port for a specific service.
    4. Use docker_services.wait_until_responsive(...) if you need to ensure a service is ready beyond the default --wait behavior.
    import pytest
    import requests
    
    @pytest.fixture(scope="session")
    def http_service(docker_ip, docker_services):
        # Resolve the host port for the 'httpbin' service on port 80
        port = docker_services.port_for("httpbin", 80)
        url = f"http://{docker_ip}:{port}"
        
        # Wait for the service to be responsive
        docker_services.wait_until_responsive(
            timeout=30.0, 
            pause=0.1, 
            check=lambda: requests.get(url).status_code == 200
        )
        return url
    
    def test_status_code(http_service):
        response = requests.get(f"{http_service}/status/418")
        assert response.status_code == 418
  2. Pin project name and manage stack lifecycle

    master

    To prevent creating multiple stacks (e.g., when debugging in an IDE) and to ensure a clean state, you can pin the project name and override the setup commands.

    Use docker_compose_project_name to set a fixed name and docker_setup to define the startup sequence (e.g., running down -v first to clear volumes).

    import pytest
    
    @pytest.fixture(scope="session")
    def docker_compose_project_name() -> str:
        return "my-compose-project"
    
    @pytest.fixture(scope="session")
    def docker_setup():
        # Stop the stack and clear volumes before starting a new one
        return ["down -v", "up --build --wait"]
  3. Install pytest-docker

    master

    Install pytest-docker using pip. By default, it uses Docker Compose V2 (docker compose).

    If you need to use the deprecated Docker Compose V1 (docker-compose), you can install the specific extra:

    pip install pytest-docker[docker-compose-v1]
    pip install pytest-docker
  4. Configure custom Docker Compose files

    master

    By default, the plugin looks for docker-compose.yml in your tests directory. You can override this by providing a docker_compose_file fixture in your conftest.py.

    To use a single custom file:

    @pytest.fixture(scope="session")
    def docker_compose_file(pytestconfig):
        return os.path.join(str(pytestconfig.rootdir), "mycustomdir", "docker-compose.yml")

    To use multiple compose files (merging them):

    @pytest.fixture(scope="session")
    def docker_compose_file(pytestconfig):
        return [
            os.path.join(str(pytestconfig.rootdir), "tests", "compose.yml"),
            os.path.join(str(pytestconfig.rootdir), "tests", "compose.override.yml"),
        ]
    import os
    import pytest
    
    @pytest.fixture(scope="session")
    def docker_compose_file(pytestconfig):
        return [os.path.join(str(pytestconfig.rootdir), "tests", "compose.yml")]
  5. Use Docker Compose V1 via fixture

    master

    If you are using the legacy docker-compose command instead of the V2 plugin, override the docker_compose_command fixture to return the string "docker-compose".

    import pytest
    
    @pytest.fixture(scope="session")
    def docker_compose_command() -> str:
        return "docker-compose"
  6. Reference: Available pytest-docker fixtures

    master

    The following fixtures are provided by the plugin. By default, they have a session scope, but you can change this using the --container-scope <scope> CLI flag.

    • docker_ip: The IP address used for TCP connections to Docker containers.
    • docker_compose_file: The absolute path to the docker-compose.yml file.
    • docker_compose_project_name: The project name used by Docker Compose.
    • docker_services: Manages the lifecycle of services (up on start, down on finish).
    • docker_compose_command: The command used to execute Docker Compose (defaults to docker compose).
    • docker_setup: A list of commands to execute during test spawn.
    • docker_cleanup: A list of commands to execute during test cleanup.
  7. Available pytest-docker fixtures and types

    master

    The pytest-docker plugin provides several fixtures to interact with Docker Compose services during testing. These include:

    • docker_compose_file: The path to the Docker Compose file being used.
    • docker_compose_project_name: The name of the Docker Compose project.
    • docker_compose_command: The command used to invoke Docker Compose.
    • docker_ip: The IP address of the Docker host.
    • docker_services: A fixture providing access to the running services.
    • docker_setup: A fixture to manage the setup of the Docker environment.
    • docker_cleanup: A fixture to manage the cleanup of the Docker environment.
    • Services: A type/class representing the collection of services available in the compose environment.
  8. Manage Docker Compose lifecycles with `docker_services`

    master

    The docker_services fixture automates the lifecycle of your integration test environment. It executes the setup command (default: up --build --wait) before your tests run and the cleanup command (default: down -v) after they finish.

    It yields a Services object that you can use to interact with the running containers.

    def test_with_docker(docker_services):
        # docker-compose up has already run
        port = docker_services.port_for("my-app", 8080)
        # ... run tests ...
        # docker-compose down -v will run automatically after this
  9. Wait for a service to become responsive with `Services.wait_until_responsive`

    master

    The Services object provides wait_until_responsive to handle the delay between starting a container and the service actually being ready to accept connections. It takes a check callable (a function that returns True when the service is ready), a timeout in seconds, and a pause interval between checks.

    def test_wait_example(docker_services):
        def is_ready():
            # logic to check if service is up (e.g., a request)
            return True
    
        docker_services.wait_until_responsive(
            check=is_ready,
            timeout=30.0,
            pause=1.0
        )
  10. Determine the IP address for TCP connections with `docker_ip`

    master

    The docker_ip fixture provides the IP address used to establish TCP connections to Docker containers. If the Docker daemon is accessed via a UNIX socket (e.g., DOCKER_HOST starts with unix://), it returns 127.0.0.1. Otherwise, it parses the DOCKER_HOST environment variable to return the appropriate host IP address.

    @pytest.fixture(scope=containers_scope)
    def docker_ip() -> Union[str, Any]:
        """Determine the IP address for TCP connections to Docker containers."""
        return get_docker_ip()
  11. Map container ports to host ports with `Services.port_for`

    master

    The Services object (provided by the docker_services fixture) includes a port_for method. This method allows you to find the dynamically assigned host port for a specific service and container port defined in your docker-compose.yml.

    For example, if your service maps 8000:80, calling port_for('service_name', 80) will return 8000.

    # Assuming `docker_services` is used in a test
    def test_service_connection(docker_services):
        host_port = docker_services.port_for("httpbin", 80)
        # Use host_port to connect to the service
  12. Override Docker Compose configuration fixtures

    master

    You can customize the Docker Compose environment by overriding the following fixtures in your test files:

    • docker_compose_command: Change the command used (e.g., from docker compose to docker-compose).
    • docker_compose_file: Provide a different path to your compose file.
    • docker_compose_project_name: Set a specific project name to avoid collisions.
    • docker_setup: Change the command used to start services.
    • docker_cleanup: Change the command used to tear down services.
    @pytest.fixture
    def docker_compose_file(pytestconfig):
        return "/path/to/custom/docker-compose.yml"
    
    @pytest.fixture
    def docker_setup():
        return ["up -d"]