Docker-based integration tests

Overview

Docker-based integration tests

PyPI version Build Status Python versions Code style

Description

Simple pytest fixtures that help you write integration tests with Docker and docker-compose. Specify all necessary containers in a docker-compose.yml file and and pytest-docker will spin them up for the duration of your tests.

This package is tested with Python versions 3.6, 3.7, 3.8 and 3.9, and pytest version 4, 5 and 6. Python 2 is not supported.

pytest-docker was originally created by André Caron.

Installation

Install pytest-docker with pip or add it to your test requirements. It is recommended to install docker-compose python package directly in your environment to ensure that it is available during tests. This will prevent potential dependency conflicts that can occur when the system wide docker-compose is used in tests.

Usage

Here is an example of a test that depends on a HTTP service.

With a docker-compose.yml file like this (using the httpbin service):

version: '2'
services:
  httpbin:
    image: "kennethreitz/httpbin"
    ports:
      - "8000:80"

You can write a test like this:

import pytest
import requests

from requests.exceptions import ConnectionError


def is_responsive(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return True
    except ConnectionError:
        return False


@pytest.fixture(scope="session")
def http_service(docker_ip, docker_services):
    """Ensure that HTTP service is up and responsive."""

    # `port_for` takes a container port and returns the corresponding host port
    port = docker_services.port_for("httpbin", 80)
    url = "http://{}:{}".format(docker_ip, port)
    docker_services.wait_until_responsive(
        timeout=30.0, pause=0.1, check=lambda: is_responsive(url)
    )
    return url


def test_status_code(http_service):
    status = 418
    response = requests.get(http_service + "/status/{}".format(status))

    assert response.status_code == status

By default this plugin will try to open docker-compose.yml in your tests directory. If you need to use a custom location, override the docker_compose_file fixture inside your conftest.py file:

import os
import pytest


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

Available fixtures

All fixtures have session scope.

docker_ip

Determine the IP address for TCP connections to Docker containers.

docker_compose_file

Get an absolute path to the docker-compose.yml file. Override this fixture in your tests if you need a custom location.

docker_compose_project_name

Generate a project name using the current process PID. Override this fixture in your tests if you need a particular project name.

docker_services

Start all services from the docker compose file (docker-compose up). After test are finished, shutdown all services (docker-compose down).

docker_cleanup

Get the docker compose command to execute for test clean-up actions. Override this fixture in your tests if you need custom clean-up actions.

Development

Use of a virtual environment is recommended. See the venv package for more information.

First, install pytest-docker and its test dependencies:

pip install -e ".[tests]"

Run tests with

pytest -c setup.cfg

to make sure that the correct configuration is used. This is also how tests are run in CI.

Use black with default settings for formatting. You can also use pylint with setup.cfg as the configuration file.

Contributing

This pytest plug-in and its source code are made available to you under a MIT license. It is safe to use in commercial and closed-source applications. Read the license for details!

Found a bug? Think a new feature would make this plug-in more practical? We welcome issues and pull requests!

When creating a pull request, be sure to follow this projects conventions (see above).

Comments
  • Bring back the fallback when run without Docker

    Bring back the fallback when run without Docker

    Could you please bring back support for running without Docker & docker-compose removed in https://github.com/AndreLouisCaron/pytest-docker/commit/37901fba65759b1d4c7d78525b9590a6b96ce5a6? I was actually using that when running integration tests and it has significant value to me.

    opened by petr-k 13
  • import `docker-compose` and `docker-py` rather than fork subprocesses

    import `docker-compose` and `docker-py` rather than fork subprocesses

    Hi, this plugin looks really cool! After briefly looking over the code and some of the issues on this project I thought I'd pop in and point out that many of the problems (not having access to service logs for example) would be simplified if it were possible to have direct access to the objects exposed in the docker-compose or docker-py library APIs. Specifically, you should be able to do almost everything you can do from the command line using this class which should provide the following benefits for this codebase:

    • allow you and pytest-docker plugin users to programmatically access service and container info
    • allow you to dispense with subprocess management in your code and focus on implementing new ways to interact with docker in pytest-idiomatic ways

    Even something as simple as a more generic version of this would be fairly helpful. Here I use the docker-py library to start up container, yield it as the test fixture object, then tear it down post-yield. In an approach like this providing access to service/container logs as requested in #13 could be a configurable behavior that occurs post-yield.

    I hope you find my suggestions helpful. I would like to try my hand at implementing this if you don't have time or don't want to yourself.

    opened by waynr 11
  • feat: allow container re-use

    feat: allow container re-use

    If the spawn and cleanup commands are skipped by overriding the fixture commands to "", None, or False then existing containers can be left around.

    Having this as a native feature would be much cleaner than how I'm currently managing this. Thank you for all the work on this project, I've used it in many others with great success :)

    opened by raddessi 10
  • tests: Fix for flake8 3.6.0

    tests: Fix for flake8 3.6.0

    With the new version of flake8 a piece of the code was raising an error. With the fix the string produces is the same, but it's less ambivalent, and flake8 likes it.

    opened by butla 8
  • add --remove-orphans to docker-compose command?

    add --remove-orphans to docker-compose command?

    I got the following error while using the plugin. Does it make sense to add --remove-orphans to the docker-compose command?

    Exception: Command 'docker-compose -f "<hidden>" -p "pytest7" down -v' returned 1: """Stopping <hidden> ... 
    E           Stopping <hidden>      ... 
    E           
    Stopping <hidden> ... done
    Stopping <hidden>      ... done
    Found orphan containers (<hidden>, <hidden>, <hidden>) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
    
    opened by vicyap 8
  • limit services started by docker_services

    limit services started by docker_services

    docker_services fixture starts all containers mentioned in the docker-compose.yaml.
    Is there some way to limit the services to some services? like docker_services(excluding_services: List)

    When in a development setting, this would be useful as we normally want to test the service with hot-reload functionality.

    I checked fixture-parameterize, but that is intended for running against all the parameters provided 1 by 1.

    opened by adityaguru149 7
  • Getting rid of tests that only test the implementation, and the path of future updates?

    Getting rid of tests that only test the implementation, and the path of future updates?

    Hey @AndreLouisCaron! I think I finally have the time to show this project some love.

    While I have some ideas for updates (e.g. #13), first I'd like to plow through the tests for this plugin itself. I think that they are tied to the implementation too much in most places, which shows in the ubiquitous mocking of subprocess.check_output. Because of that, they will all probably break with any serious refactoring.

    I think we can have a 100% coverage with like two tests that actually run docker_services and a couple tests for discrete functions. That should demonstrate that the plugin is actually working.

    I plan to submit a couple of self-contained but chained (one depending upon the other) pull requests. And, of course, I'll be mindful of not breaking anything for the current users. What do you think about this whole thing?

    I'm already tackling the problems (https://github.com/pytest-dev/pytest/issues/4193), but I wanted to hear your thoughts on that before I go far ahead.

    opened by butla 7
  • Unique container names are bad

    Unique container names are bad

    I know I can override container names, but I think the default implementation is bad.

    Currently containers are named "pytest{}".format(os.getpid()). This leads to the following problems:

    • If test runs fail or are interrupted (ctrl-c), containers may stay around. Due to the unique names they start to accumulate. Same for images.
    • Docker-compose doesn't recognize that things belong to each other, and has conflicts. For example, if you use a static network subnet, then pytest-docker fails as different runs try to use the same network, but it's named separately.

    This cost me days to debug (no docker wiz here).

    Is there a specific reason why to use the pid-based naming? I would follow simply the behavior of docker-compose:

    @pytest.fixture(scope='session')
    def docker_compose_project_name(pytestconfig):
        """ Generate a project name using the projects root directory.
    
        Override this fixture in your tests if you need a particular project name.
        """
        return "{}pytest".format(os.path.basename(str(pytestconfig.rootdir)))
    
    opened by arnuschky 7
  • Fallback still starts the container with compose?

    Fallback still starts the container with compose?

    My impression from the wording around docker_allow_fallback would be that if it returns True it doesn't try to run anything with Compose at all and just returns localhost:port, where port comes from the port mapping in the docker-compose.yaml file for that service.

    However, looking at https://github.com/AndreLouisCaron/pytest-docker/blob/master/src/pytest_docker/init.py#L169-L175 it seems that when fallback returns True but docker ps still works successfully it happily goes on and still ends up doing L178, spinning up the containers through compose. It seems that only when fallback is True and docker ps fails it does what I would expect.

    This seems very strange to me. If I say it's supposed to use the fallback, why still check if docker is available and then ignore what the user configured?

    I would expect the code like this instead:

    
        if docker_allow_fallback is True:
                # Run against localhost
                yield Services(docker_compose, docker_allow_fallback=True)
                return
    
    opened by daenney 5
  • Allow fixture to return multiple compose files

    Allow fixture to return multiple compose files

    As described in https://docs.docker.com/compose/extends/, docker compose can be passed multiple compose files that are then merged. This change allows the docker_compose_file fixture to make use of this by returning e.g. a list or a tuple.

    opened by gdetrez 5
  • Remove attrs constraint

    Remove attrs constraint

    attrs is required by pytest but we should be able to remove the <22 constraint since it isn't directly used by this package

    pytest-docker==1.0.0
      - pytest [required: >=4.0,<8.0, installed: 7.1.2]
        - attrs [required: >=19.2.0, installed: 22.1.0]
        - iniconfig [required: Any, installed: 1.1.1]
        - packaging [required: Any, installed: 21.3]
          - pyparsing [required: >=2.0.2,!=3.0.5, installed: 3.0.9]
        - pluggy [required: >=0.12,<2.0, installed: 1.0.0]
        - py [required: >=1.8.2, installed: 1.11.0]
        - tomli [required: >=1.0.0, installed: 2.0.1]
    pytest-pycodestyle==2.3.0
      - pycodestyle [required: Any, installed: 2.9.1]
      - pytest [required: >=7.0, installed: 7.1.2]
        - attrs [required: >=19.2.0, installed: 22.1.0]
        - iniconfig [required: Any, installed: 1.1.1]
        - packaging [required: Any, installed: 21.3]
          - pyparsing [required: >=2.0.2,!=3.0.5, installed: 3.0.9]
        - pluggy [required: >=0.12,<2.0, installed: 1.0.0]
        - py [required: >=1.8.2, installed: 1.11.0]
        - tomli [required: >=1.0.0, installed: 2.0.1]
    pytest-pylint==0.18.0
      - pylint [required: >=2.3.0, installed: 2.14.5]
        - astroid [required: >=2.11.6,<=2.12.0-dev0, installed: 2.11.7]
          - lazy-object-proxy [required: >=1.4.0, installed: 1.7.1]
          - setuptools [required: >=20.0, installed: 58.1.0]
          - wrapt [required: >=1.11,<2, installed: 1.14.1]
        - dill [required: >=0.2, installed: 0.3.5.1]
        - isort [required: >=4.2.5,<6, installed: 5.10.1]
        - mccabe [required: >=0.6,<0.8, installed: 0.7.0]
        - platformdirs [required: >=2.2.0, installed: 2.5.2]
        - tomli [required: >=1.1.0, installed: 2.0.1]
        - tomlkit [required: >=0.10.1, installed: 0.11.1]
      - pytest [required: >=5.4, installed: 7.1.2]
        - attrs [required: >=19.2.0, installed: 22.1.0]
    
    
    opened by Ryan-Bell 4
  • Allow for overriding of pytest fixture scope

    Allow for overriding of pytest fixture scope

    Being able to override the pytest fixtures to have a "function" scope instead of "session" scope would be useful when using containers that may carry state between tests.

    An example of a useful configuration (though hard-coded): https://github.com/Greenlight-Analytical/pytest-docker/commit/102eedb65d3ff8a9cddc285363bd31d6e8e45f15

    Some boilerplate to connect a flag:

    def pytest_addoption(parser):
        parser.addoption(
            "--keep-containers",
            action="store_true",
            help="Keep containers between tests. This is faster but may cause test failures from leftover state between tests.",
        )
    
    
    @pytest.fixture
    def keep_containers(request):
        return request.config.getoption("--keep-containers")
    
    
    def keep_containers_scope(fixture_name, config):
        if config.getoption("--keep-containers", None):
            return "session"
        return "function"
    
    
    @pytest.fixture(scope=keep_containers_scope)
    # This annotation can apply to all fixtures, instead of the hard-coded "session" scope
    
    opened by austinkeller 0
  • feat: raise a more specific exception on timeout

    feat: raise a more specific exception on timeout

    This provides a narrower exception to catch when calling wait_until_responsive.

    Context: when an entire testsuite relies on a service, I'd like to call pytest.exit() if it times out so that a useful error message is displayed for the user instead of failing the fixture setup for all tests and producing a wall of text.

    /cc @Luminaar

    opened by nejch 0
  • Insufficient removal of control characters on windows with cmd.exe as shell

    Insufficient removal of control characters on windows with cmd.exe as shell

    Affected Line https://github.com/avast/pytest-docker/blob/567fa091a0d8ccf4ac2e8897c1ccecf08774125c/src/pytest_docker/plugin.py#L79 endpoint = output.strip().decode("utf-8") Affected Version: at least since 0.10.3

    I use PyCharm on Windows 10 to debug my pytest test cases. Starting the conatiners the above line is executed to to resolve a port for a service.

    output = self._docker_compose.execute("port %s %d" % (service, container_port)) results to b'0.0.0.0:12347\r\n\x1b[0m'. No control character is removed. Hence, output.strip().decode("utf-8") result to s string including the control characters.

    Some lines later (L86) if len(endpoint.split("\n")) > 1: does not support windows line endings.

    On Linux or Git Bash for Windows, there is no problem.

    My fix for that. Please check this solution:

    endpoint = endpoint.replace("\r",'') # add support for windows line ending
    if len(endpoint.split("\n")) > 1:
          endpoint = endpoint.split("\n")[-1] # index -1 is also wrong for me because it results to '\x1b[0m'. Maybe use 0 or -2
    
    opened by mf01 2
  • Endpoint empty error

    Endpoint empty error

    On a windows system, the endpoint command returns a weird string:

    0.0.0.0:9997\r\n

    That's why the strip here does not change the string, and this line takes the garbage part. I have solved it by using a regexp:

    ips = re.findall(r'[0-9]+(?:\.[0-9]+){3}:[0-9]+', endpoint)
    assert len(ips) == 1
    endpoint = ips[0]
    

    Do you think this is a possible solution that could be merged in?

    opened by didacrarbones 5
  • Docs for multiple docker-compose files

    Docs for multiple docker-compose files

    pytest-docker already supports shared compose files. However I couldn't find it in the docs. Is it really missing? If so, can I send a PR to add something to the README?

    opened by edinhodiluviano 4
Releases(0.12.0)
Owner
Avast
https://avast.github.io
Avast
Let your Python tests travel through time

FreezeGun: Let your Python tests travel through time FreezeGun is a library that allows your Python tests to travel through time by mocking the dateti

Steve Pulec 3.5k Dec 29, 2022
User-oriented Web UI browser tests in Python

Selene - User-oriented Web UI browser tests in Python (Selenide port) Main features: User-oriented API for Selenium Webdriver (code like speak common

Iakiv Kramarenko 575 Jan 2, 2023
The pytest framework makes it easy to write small tests, yet scales to support complex functional testing

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. An example o

pytest-dev 9.6k Jan 2, 2023
Data-Driven Tests for Python Unittest

DDT (Data-Driven Tests) allows you to multiply one test case by running it with different test data, and make it appear as multiple test cases. Instal

null 424 Nov 28, 2022
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
:game_die: Pytest plugin to randomly order tests and control random.seed

pytest-randomly Pytest plugin to randomly order tests and control random.seed. Features All of these features are on by default but can be disabled wi

pytest-dev 471 Dec 30, 2022
Selects tests affected by changed files. Continous test runner when used with pytest-watch.

This is a pytest plug-in which automatically selects and re-executes only tests affected by recent changes. How is this possible in dynamic language l

Tibor Arpas 614 Dec 30, 2022
To automate the generation and validation tests of COSE/CBOR Codes and it's base45/2D Code representations

To automate the generation and validation tests of COSE/CBOR Codes and it's base45/2D Code representations, a lot of data has to be collected to ensure the variance of the tests. This respository was established to collect a lot of different test data and related test cases of different member states in a standardized manner. Each member state can generate a folder in this section.

null 160 Jul 25, 2022
Show surprise when tests are passing

pytest-pikachu pytest-pikachu prints ascii art of Surprised Pikachu when all tests pass. Installation $ pip install pytest-pikachu Usage Pass the --p

Charlie Hornsby 13 Apr 15, 2022
Django-google-optimize is a Django application designed to make running server side Google Optimize A/B tests easy.

Django-google-optimize Django-google-optimize is a Django application designed to make running Google Optimize A/B tests easy. Here is a tutorial on t

Adin Hodovic 39 Oct 25, 2022
Run ISP speed tests and save results

SpeedMon Automatically run periodic internet speed tests and save results to a variety of storage backends. Supported Backends InfluxDB v1 InfluxDB v2

Matthew Carey 9 May 8, 2022
LuluTest is a Python framework for creating automated browser tests.

LuluTest LuluTest is an open source browser automation framework using Python and Selenium. It is relatively lightweight in that it mostly provides wr

Erik Whiting 14 Sep 26, 2022
Given some test cases, this program automatically queries the oracle and tests your Cshanty compiler!

The Diviner A complement to The Oracle for compilers class. Given some test cases, this program automatically queries the oracle and tests your compil

Grant Holmes 2 Jan 29, 2022
Statistical tests for the sequential locality of graphs

Statistical tests for the sequential locality of graphs You can assess the statistical significance of the sequential locality of an adjacency matrix

null 2 Nov 23, 2021
Fail tests that take too long to run

GitHub | PyPI | Issues pytest-fail-slow is a pytest plugin for making tests fail that take too long to run. It adds a --fail-slow DURATION command-lin

John T. Wodder II 4 Nov 27, 2022
Automated tests for OKAY websites in Python (Selenium) - user friendly version

Okay Selenium Testy Aplikace určená k testování produkčních webů společnosti OKAY s.r.o. Závislosti K běhu aplikace je potřeba mít v počítači nainstal

Viktor Bem 0 Oct 1, 2022
a wrapper around pytest for executing tests to look for test flakiness and runtime regression

bubblewrap a wrapper around pytest for assessing flakiness and runtime regressions a cs implementations practice project How to Run: First, install de

Anna Nagy 1 Aug 5, 2021
Travel through time in your tests.

time-machine Travel through time in your tests. A quick example: import datetime as dt

Adam Johnson 373 Dec 27, 2022
A library to make concurrent selenium tests that automatically download and setup webdrivers

AutoParaSelenium A library to make parallel selenium tests that automatically download and setup webdrivers Usage Installation pip install autoparasel

Ronak Badhe 8 Mar 13, 2022