diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e9d3ed25..ca8bf12a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,7 @@ jobs: - keycloak.py - arangodb.py - azurite.py + - mockserver.py runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/README.rst b/README.rst index e77d6b125..b10c56f3b 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,7 @@ Currently available features: * RabbitMQ * Keycloak * Azurite container +* MockServer container Installation ------------ diff --git a/requirements.in b/requirements.in index 90406ba20..4e839db91 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ --e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite] +-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver,neo4j,kafka,rabbitmq,clickhouse,keycloak,arangodb,azurite,mockserver] codecov>=2.1.0 cryptography<37 flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages. diff --git a/requirements/3.10.txt b/requirements/3.10.txt index 2f3583989..5b2695066 100644 --- a/requirements/3.10.txt +++ b/requirements/3.10.txt @@ -34,7 +34,7 @@ bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -82,11 +82,13 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.0rc9 + # via trio flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.12.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -99,15 +101,17 @@ greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.49.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 - # via google-api-core -h11==0.13.0 +grpcio-status==1.48.2 + # via + # google-api-core + # testcontainers +h11==0.14.0 # via wsproto idna==3.4 # via @@ -129,6 +133,8 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +mockserver-client==0.0.6 + # via testcontainers msrest==0.7.1 # via azure-storage-blob neo4j==5.0.1 @@ -197,9 +203,9 @@ pytest==7.1.3 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.0 # via testcontainers python-dotenv==0.21.0 # via docker-compose @@ -225,6 +231,7 @@ requests==2.28.1 # docker # docker-compose # google-api-core + # mockserver-client # msrest # python-arango # python-keycloak @@ -243,7 +250,7 @@ rsa==4.9 # python-jose scramp==1.4.1 # via pg8000 -selenium==4.4.3 +selenium==4.5.0 # via testcontainers six==1.16.0 # via @@ -262,7 +269,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.2.2 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -284,7 +291,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket @@ -292,7 +299,7 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.2 +tzdata==2022.4 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver diff --git a/requirements/3.7.txt b/requirements/3.7.txt index 9d778343e..9a825ba70 100644 --- a/requirements/3.7.txt +++ b/requirements/3.7.txt @@ -40,7 +40,7 @@ cached-property==1.5.2 # via docker-compose cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -88,11 +88,13 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.0rc9 + # via trio flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.12.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -105,15 +107,17 @@ greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.49.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 - # via google-api-core -h11==0.13.0 +grpcio-status==1.48.2 + # via + # google-api-core + # testcontainers +h11==0.14.0 # via wsproto idna==3.4 # via @@ -144,6 +148,8 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +mockserver-client==0.0.6 + # via testcontainers msrest==0.7.1 # via azure-storage-blob neo4j==5.0.1 @@ -212,9 +218,9 @@ pytest==7.1.3 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.0 # via testcontainers python-dotenv==0.21.0 # via docker-compose @@ -240,6 +246,7 @@ requests==2.28.1 # docker # docker-compose # google-api-core + # mockserver-client # msrest # python-arango # python-keycloak @@ -258,7 +265,7 @@ rsa==4.9 # python-jose scramp==1.4.1 # via pg8000 -selenium==4.4.3 +selenium==4.5.0 # via testcontainers six==1.16.0 # via @@ -277,7 +284,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.2.2 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -299,7 +306,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket @@ -312,7 +319,7 @@ typing-extensions==4.3.0 # h11 # importlib-metadata # redis -tzdata==2022.2 +tzdata==2022.4 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver diff --git a/requirements/3.8.txt b/requirements/3.8.txt index 3f3db8b8a..b02f5884e 100644 --- a/requirements/3.8.txt +++ b/requirements/3.8.txt @@ -38,7 +38,7 @@ bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -86,11 +86,13 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.0rc9 + # via trio flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.12.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -103,15 +105,17 @@ greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.49.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 - # via google-api-core -h11==0.13.0 +grpcio-status==1.48.2 + # via + # google-api-core + # testcontainers +h11==0.14.0 # via wsproto idna==3.4 # via @@ -135,6 +139,8 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +mockserver-client==0.0.6 + # via testcontainers msrest==0.7.1 # via azure-storage-blob neo4j==5.0.1 @@ -203,9 +209,9 @@ pytest==7.1.3 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.0 # via testcontainers python-dotenv==0.21.0 # via docker-compose @@ -231,6 +237,7 @@ requests==2.28.1 # docker # docker-compose # google-api-core + # mockserver-client # msrest # python-arango # python-keycloak @@ -249,7 +256,7 @@ rsa==4.9 # python-jose scramp==1.4.1 # via pg8000 -selenium==4.4.3 +selenium==4.5.0 # via testcontainers six==1.16.0 # via @@ -268,7 +275,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.2.2 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -290,7 +297,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket @@ -298,7 +305,7 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.2 +tzdata==2022.4 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver diff --git a/requirements/3.9.txt b/requirements/3.9.txt index 2ecfcd63c..5e2217c8f 100644 --- a/requirements/3.9.txt +++ b/requirements/3.9.txt @@ -34,7 +34,7 @@ bcrypt==4.0.0 # via paramiko cachetools==5.2.0 # via google-auth -certifi==2022.9.14 +certifi==2022.9.24 # via # msrest # requests @@ -82,11 +82,13 @@ ecdsa==0.18.0 # via python-jose entrypoints==0.3 # via flake8 +exceptiongroup==1.0.0rc9 + # via trio flake8==3.7.9 # via -r requirements.in google-api-core[grpc]==2.10.1 # via google-cloud-pubsub -google-auth==2.11.1 +google-auth==2.12.0 # via google-api-core google-cloud-pubsub==1.7.2 # via testcontainers @@ -99,15 +101,17 @@ greenlet==1.1.3 # via sqlalchemy grpc-google-iam-v1==0.12.4 # via google-cloud-pubsub -grpcio==1.48.1 +grpcio==1.49.1 # via # google-api-core # googleapis-common-protos # grpc-google-iam-v1 # grpcio-status -grpcio-status==1.48.1 - # via google-api-core -h11==0.13.0 +grpcio-status==1.48.2 + # via + # google-api-core + # testcontainers +h11==0.14.0 # via wsproto idna==3.4 # via @@ -131,6 +135,8 @@ markupsafe==2.1.1 # via jinja2 mccabe==0.6.1 # via flake8 +mockserver-client==0.0.6 + # via testcontainers msrest==0.7.1 # via azure-storage-blob neo4j==5.0.1 @@ -199,9 +205,9 @@ pytest==7.1.3 # via # -r requirements.in # pytest-cov -pytest-cov==3.0.0 +pytest-cov==4.0.0 # via -r requirements.in -python-arango==7.4.1 +python-arango==7.5.0 # via testcontainers python-dotenv==0.21.0 # via docker-compose @@ -227,6 +233,7 @@ requests==2.28.1 # docker # docker-compose # google-api-core + # mockserver-client # msrest # python-arango # python-keycloak @@ -245,7 +252,7 @@ rsa==4.9 # python-jose scramp==1.4.1 # via pg8000 -selenium==4.4.3 +selenium==4.5.0 # via testcontainers six==1.16.0 # via @@ -264,7 +271,7 @@ snowballstemmer==2.2.0 # via sphinx sortedcontainers==2.4.0 # via trio -sphinx==5.1.1 +sphinx==5.2.2 # via -r requirements.in sphinxcontrib-applehelp==1.0.2 # via sphinx @@ -286,7 +293,7 @@ tomli==2.0.1 # via # coverage # pytest -trio==0.21.0 +trio==0.22.0 # via # selenium # trio-websocket @@ -294,7 +301,7 @@ trio-websocket==0.9.2 # via selenium typing-extensions==4.3.0 # via azure-core -tzdata==2022.2 +tzdata==2022.4 # via pytz-deprecation-shim tzlocal==4.2 # via clickhouse-driver diff --git a/setup.py b/setup.py index fe8428993..c60abd81d 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ 'oracle': ['sqlalchemy', 'cx_Oracle'], 'postgresql': ['sqlalchemy', 'psycopg2-binary'], 'selenium': ['selenium'], - 'google-cloud-pubsub': ['google-cloud-pubsub < 2'], + 'google-cloud-pubsub': ['grpcio-status < 1.49', 'google-cloud-pubsub < 2'], 'mongo': ['pymongo'], 'redis': ['redis'], 'mssqlserver': ['pymssql'], @@ -69,6 +69,7 @@ 'keycloak': ['python-keycloak'], 'arangodb': ['python-arango'], 'azurite': ['azure-storage-blob'], + 'mockserver': ['mockserver-client'], }, long_description_content_type="text/x-rst", long_description=long_description, diff --git a/testcontainers/mockserver.py b/testcontainers/mockserver.py new file mode 100644 index 000000000..2f6a3f8a3 --- /dev/null +++ b/testcontainers/mockserver.py @@ -0,0 +1,50 @@ +from typing import Any + +import requests +from mockserver import MockServerClient + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + + +class MockServerContainer(DockerContainer): + """ + MockServer container. + + Example + ------- + :: + + with MockServerContainer() as mockserver: + mockserver_client = mockserver.get_client() + mockserver_client.stub(request(method='GET', path='/path'), + response=response(code=HTTPStatus.OK)) + + response = requests.get(f'{mockserver.base_url}/path') + assert response.status_code == HTTPStatus.OK + + mockserver_client.verify() + """ + + EDGE_PORT = 1080 + IMAGE = 'mockserver/mockserver:latest' + + def __init__(self, image: str = IMAGE, **kwargs: Any): + super().__init__(image, **kwargs) + self.with_exposed_ports(MockServerContainer.EDGE_PORT) + + @property + def base_url(self) -> str: + return f'http://{self.get_container_host_ip()}:{self.get_exposed_port(self.EDGE_PORT)}' + + def get_client(self) -> MockServerClient: + return MockServerClient(f'{self.base_url}/mockserver') + + @wait_container_is_ready(requests.ConnectionError) + def _connect(self): + requests.get(self.base_url) + + def start(self): + super().start() + self._connect() + return self diff --git a/tests/test_mockserver.py b/tests/test_mockserver.py new file mode 100644 index 000000000..348c2f637 --- /dev/null +++ b/tests/test_mockserver.py @@ -0,0 +1,18 @@ +from http import HTTPStatus + +import requests +from mockserver import request as request_expectation, response as response_expectation + +from testcontainers.mockserver import MockServerContainer + + +def test_mockserver() -> None: + with MockServerContainer() as mockserver: + mockserver_client = mockserver.get_client() + mockserver_client.stub(request_expectation(method='GET', path='/path'), + response=response_expectation(code=HTTPStatus.OK)) + + response = requests.get(f'{mockserver.base_url}/path') + assert response.status_code == HTTPStatus.OK + + mockserver_client.verify()