Create simple Pytest fixtures for writing integration tests based on Docker containers. The framework provides a service class to start and stop containers based Docker Compose. Each single container can be started individually.
Some parts of this package are taken from https://github.com/AndreLouisCaron/pytest-docker
The docker compose file should contain all desired containers and the ports
should be exposed. In the following example we want to start the app to test
and a SQL database (Crate). Let's assume there is a Dockerfile for the app
in the same folder as the docker compose file:
version: "3"
services:
app:
build: .
ports:
- "8080"
depends_on:
- "crate"
crate:
image: crate:latest
ports:
- "4200"
In the conftest.py file we can declare the docker fixtures for each service
we want to be able to start in the tests:
import pytest
@pytest.fixture(scope='session')
def docker_app(docker_services):
docker_services.start('app')
public_port = docker_services.wait_for_service("app", 8080)
url = "http://{docker_services.docker_ip}:{public_port}".format(**locals())
return url
@pytest.fixture(scope='session')
def docker_crate(docker_services):
docker_services.start('crate')
public_port = docker_services.wait_for_service("crate", 4200)
dsn = "{docker_services.docker_ip}:{public_port}".format(**locals())
return dsn
By default the fixture will look for the docker-compose.yml file in the
tests subfolder of the path where pytest.ini resides (or the project's
root directory if no ini file is given - as in the tests example). In many
cases you will want to override the location for the docker compose files. Just
overwrite the docker_compose_files fixture in your conftest.py file:
@pytest.fixture(scope='session')
def docker_compose_files(pytestconfig):
"""Get the docker-compose.yml absolute path.
Override this fixture in your tests if you need a custom location.
"""
return [
project_path('docker', 'docker-compose.yml'),
]
In your test file declare the fixtures you want to use:
def test_something(docker_app, docker_crate):
# e.g. initialize database
...
# test something (e.g. request to docker_app)
...
A working configuration and test example can be found in the tests folder
of this package.
It's possible to execute a command inside one of the Docker containers. Use
the exec method of the docker_services fixture:
def test_exec(docker_services):
# the first argument is the service name of the compose file,
# the following arguments build the command to run
res = docker_services.exec('crate', 'ls', '-a')
The wait_for_service method of the service module checks whether the
docker service is really started. By default it makes a HTTP GET request to the
server's / endpoint. The service will retry to check until a timeout of
30 seconds has passed.
Some services may work differently and require a custom checker.
Create a custom service checker function which receives the IP address and the port as parameters:
def custom_service_checker(ip_address, port):
# if service is ready
return True
# otherwise return False
In the fixture provide the custom service checker function as check_service
parameter to the wait_for_service method:
@pytest.fixture(scope='session')
def docker_custom_service(docker_services):
docker_services.start('custom_service')
public_port = docker_services.wait_for_service(
"app",
8080,
check_server=custom_service_checker
)
url = "http://{docker_services.docker_ip}:{public_port}".format(**locals())
return url
To use another request path with the default checker the url_checker method can be used to create a check_url method for another path:
docker_services.wait_for_service(
"app",
8080,
check_server=url_checker('/probe_status'),
)
Tests are held in the tests directory. Running tests is done via the
pytest package with:
./gradlew pytest