Skip to content

Commit

Permalink
Make compatible with pytest-asyncio 0.17 (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov authored Jan 20, 2022
1 parent e22c265 commit 3de013a
Show file tree
Hide file tree
Showing 13 changed files with 694 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,8 @@ ENV/

# Rope project settings
.ropeproject

.python-version

# generated by setuptools_scm
pytest_asyncio/_version.py
67 changes: 67 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-merge-conflict
exclude: rst$
- repo: https://github.com/asottile/yesqa
rev: v1.3.0
hooks:
- id: yesqa
- repo: https://github.com/Zac-HD/shed
rev: 0.6.0 # 0.7 does not support Python 3.7
hooks:
- id: shed
args:
- --refactor
- --py37-plus
types_or:
- python
- markdown
- rst
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
rev: 0.1.0
hooks:
- id: yamlfmt
args: [--mapping, '2', --sequence, '2', --offset, '0']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: fix-encoding-pragma
args: [--remove]
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: debug-statements
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
language_version: python3
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
hooks:
- id: python-use-type-annotations
- repo: https://github.com/rhysd/actionlint
rev: v1.6.8
hooks:
- id: actionlint-docker
args:
- -ignore
- 'SC2155:'
- -ignore
- 'SC2086:'
- -ignore
- 'SC1004:'
- repo: https://github.com/sirosen/check-jsonschema
rev: 0.9.1
hooks:
- id: check-github-actions
ci:
skip:
- actionlint-docker
- check-github-actions
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
CHANGES
=======

1.0.0 (2022-1-20)
------------------

- The plugin is compatible with ``pytest-asyncio`` now. It uses ``pytest-asyncio`` for
async tests running and async fixtures support, providing by itself only fixtures for
creating aiohttp test server and client.

0.2.0 (2017-11-30)
------------------

Expand Down
41 changes: 29 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,54 @@ pytest-aiohttp

pytest plugin for aiohttp support

The library allows to use `aiohttp pytest plugin
<http://aiohttp.readthedocs.io/en/stable/testing.html#pytest-example>`_
without need for explicit loading it like `pytest_plugins =
'aiohttp.pytest_plugin'`.
The library provides useful fixtures for creation test aiohttp server and client.




Just run:
Installation
------------

.. code-block:: console
$ pip install pytest-aiohttp
and write tests with the plugin support:
Add ``asyncio_mode = auto`` line to `pytest configuration
<https://docs.pytest.org/en/latest/customize.html>`_ (see `pytest-asyncio modes
<https://github.com/pytest-dev/pytest-asyncio#modes>`_ for details). The plugin works
with ``strict`` mode also.



Usage
-----

Write tests in `pytest-asyncio <https://github.com/pytest-dev/pytest-asyncio>`_ style
using provided fixtures for aiohttp test server and client creation. The plugin provides
resources cleanup out-of-the-box.

The simple usage example:

.. code-block:: python
from aiohttp import web
async def hello(request):
return web.Response(body=b'Hello, world')
return web.Response(body=b"Hello, world")
def create_app(loop):
app = web.Application(loop=loop)
app.router.add_route('GET', '/', hello)
app.router.add_route("GET", "/", hello)
return app
async def test_hello(test_client):
client = await test_client(create_app)
resp = await client.get('/')
resp = await client.get("/")
assert resp.status == 200
text = await resp.text()
assert 'Hello, world' in text
assert "Hello, world" in text
See `aiohttp documentation <https://docs.aiohttp.org/en/stable/testing.html#pytest>` for
more details about fixtures usage.
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[build-system]
requires = [
"setuptools>=51.0",
"wheel>=0.36",
"setuptools_scm>=6.2"
]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]
write_to = "pytest_aiohttp/_version.py"
5 changes: 1 addition & 4 deletions pytest_aiohttp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
__version__ = '0.3.0'


from aiohttp.pytest_plugin import *
from ._version import version as __version__ # noqa
4 changes: 4 additions & 0 deletions pytest_aiohttp/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# file generated by setuptools_scm
# don't change, don't track in version control
version = "0.3.1.dev2+ge22c265.d20220120"
version_tuple = (0, 3, 1, "dev2", "ge22c265.d20220120")
172 changes: 172 additions & 0 deletions pytest_aiohttp/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import asyncio
import warnings
from typing import Any, Awaitable, Callable, Dict, Generator, Optional, Type, Union

import pytest
import pytest_asyncio
from aiohttp.test_utils import BaseTestServer, RawTestServer, TestClient, TestServer
from aiohttp.web import Application, BaseRequest, StreamResponse

AiohttpClient = Callable[[Union[Application, BaseTestServer]], Awaitable[TestClient]]


LEGACY_MODE = DeprecationWarning(
"The 'asyncio_mode' is 'legacy', switching to 'auto' for the sake of "
"pytest-aiohttp backward compatibility. "
"Please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' "
"in pytest configuration file."
)


@pytest.mark.tryfirst
def pytest_configure(config) -> None:
val = config.getoption("asyncio_mode")
if val is None:
val = config.getini("asyncio_mode")
if val == "legacy":
config.option.asyncio_mode = "auto"
config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)


@pytest.fixture
def loop(event_loop: asyncio.AbstractEventLoop) -> asyncio.AbstractEventLoop:
warnings.warn(
"'loop' fixture is deprecated and scheduled for removal, "
"please use 'event_loop' instead",
DeprecationWarning,
)
return loop


@pytest.fixture
def proactor_loop(event_loop: asyncio.AbstractEventLoop) -> asyncio.AbstractEventLoop:
warnings.warn(
"'proactor_loop' fixture is deprecated and scheduled for removal, "
"please use 'event_loop' instead",
DeprecationWarning,
)
return loop


@pytest.fixture
def aiohttp_unused_port(
unused_tcp_port_factory: Callable[[], int]
) -> Callable[[], int]:
warnings.warn(
"'aiohttp_unused_port' fixture is deprecated "
"and scheduled for removal, "
"please use 'unused_tcp_port_factory' instead",
DeprecationWarning,
)
return unused_tcp_port_factory


@pytest_asyncio.fixture
async def aiohttp_server() -> Callable[..., Awaitable[TestServer]]:
"""Factory to create a TestServer instance, given an app.
aiohttp_server(app, **kwargs)
"""
servers = []

async def go(
app: Application, *, port: Optional[int] = None, **kwargs: Any
) -> TestServer:
server = TestServer(app, port=port)
await server.start_server(**kwargs)
servers.append(server)
return server

yield go

while servers:
await servers.pop().close()


@pytest_asyncio.fixture
async def aiohttp_raw_server() -> Callable[..., Awaitable[RawTestServer]]:
"""Factory to create a RawTestServer instance, given a web handler.
aiohttp_raw_server(handler, **kwargs)
"""
servers = []

async def go(
handler: Callable[[BaseRequest], Awaitable[StreamResponse]],
*,
port: Optional[int] = None,
**kwargs: Any,
) -> RawTestServer:
server = RawTestServer(handler, port=port)
await server.start_server(**kwargs)
servers.append(server)
return server

yield go

while servers:
await servers.pop().close()


@pytest.fixture
def aiohttp_client_cls() -> Type[TestClient]:
"""
Client class to use in ``aiohttp_client`` factory.
Use it for passing custom ``TestClient`` implementations.
Example::
class MyClient(TestClient):
async def login(self, *, user, pw):
payload = {"username": user, "password": pw}
return await self.post("/login", json=payload)
@pytest.fixture
def aiohttp_client_cls():
return MyClient
def test_login(aiohttp_client):
app = web.Application()
client = await aiohttp_client(app)
await client.login(user="admin", pw="s3cr3t")
"""
return TestClient


@pytest.fixture
async def aiohttp_client(
aiohttp_client_cls: Type[TestClient],
) -> Generator[AiohttpClient, None, None]:
"""Factory to create a TestClient instance.
aiohttp_client(app, **kwargs)
aiohttp_client(server, **kwargs)
aiohttp_client(raw_server, **kwargs)
"""
clients = []

async def go(
__param: Union[Application, BaseTestServer],
*,
server_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> TestClient:
if isinstance(__param, Application):
server_kwargs = server_kwargs or {}
server = TestServer(__param, **server_kwargs)
client = aiohttp_client_cls(server, **kwargs)
elif isinstance(__param, BaseTestServer):
client = aiohttp_client_cls(__param, **kwargs)
else:
raise ValueError("Unknown argument type: %r" % type(__param))

await client.start_server()
clients.append(client)
return client

yield go

while clients:
await clients.pop().close()
Loading

0 comments on commit 3de013a

Please sign in to comment.