Skip to content

Commit 39c1f9d

Browse files
committed
Support async tests which use Hypothesis
1 parent 923fe9a commit 39c1f9d

File tree

5 files changed

+63
-2
lines changed

5 files changed

+63
-2
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var/
2222
*.egg-info/
2323
.installed.cfg
2424
*.egg
25+
.hypothesis/
2526

2627
# PyInstaller
2728
# Usually these files are written by a python script from a template

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ matrix:
1212
dist: xenial
1313
sudo: true
1414

15-
install: pip install tox-travis coveralls
15+
install: pip install tox-travis coveralls hypothesis
1616

1717
script: tox -e $TOX_ENV
1818

README.rst

+3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ Changelog
178178

179179
0.10.0. (UNRELEASED)
180180
~~~~~~~~~~~~~~~~~~~~
181+
- ``pytest-asyncio`` integrates with `Hypothesis <https://hypothesis.readthedocs.io>`_
182+
to support ``@given`` on async test functions using ``asyncio``.
183+
`#102` <https://github.com/pytest-dev/pytest-asyncio/pull/102>
181184

182185
0.9.0 (2018-07-28)
183186
~~~~~~~~~~~~~~~~~~

pytest_asyncio/plugin.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""pytest-asyncio implementation."""
22
import asyncio
33
import contextlib
4+
import functools
45
import inspect
56
import socket
67

@@ -139,7 +140,8 @@ def pytest_pyfunc_call(pyfuncitem):
139140
function call.
140141
"""
141142
for marker_name, fixture_name in _markers_2_fixtures.items():
142-
if marker_name in pyfuncitem.keywords:
143+
if inspect.isasyncgenfunction(pyfuncitem.obj) \
144+
and marker_name in pyfuncitem.keywords:
143145
event_loop = pyfuncitem.funcargs[fixture_name]
144146

145147
funcargs = pyfuncitem.funcargs
@@ -152,11 +154,39 @@ def pytest_pyfunc_call(pyfuncitem):
152154
return True
153155

154156

157+
def wrap_in_sync(func):
158+
"""Return a sync wrapper around an async function."""
159+
160+
@functools.wraps(func)
161+
def inner(**kwargs):
162+
loop = asyncio.get_event_loop_policy().new_event_loop()
163+
try:
164+
coro = func(**kwargs)
165+
if coro is not None:
166+
future = asyncio.ensure_future(coro, loop=loop)
167+
loop.run_until_complete(future)
168+
finally:
169+
loop.close()
170+
171+
return inner
172+
173+
155174
def pytest_runtest_setup(item):
156175
for marker, fixture in _markers_2_fixtures.items():
157176
if marker in item.keywords and fixture not in item.fixturenames:
158177
# inject an event loop fixture for all async tests
159178
item.fixturenames.append(fixture)
179+
if item.get_closest_marker("asyncio") is not None:
180+
if hasattr(item.obj, 'hypothesis'):
181+
# If it's a Hypothesis test, we insert the wrap_in_sync decorator
182+
item.obj.hypothesis.inner_test = wrap_in_sync(
183+
item.obj.hypothesis.inner_test
184+
)
185+
elif getattr(item.obj, 'is_hypothesis_test', False):
186+
pytest.fail(
187+
'test function `%r` is using Hypothesis, but pytest-asyncio '
188+
'only works with Hypothesis 3.64.0 or later.' % item
189+
)
160190

161191

162192
# maps marker to the name of the event loop fixture that will be available

tests/test_hypothesis_integration.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Tests for the Hypothesis integration, which wraps async functions in a
2+
sync shim for Hypothesis.
3+
"""
4+
5+
import pytest
6+
7+
from hypothesis import given, strategies as st
8+
9+
10+
@given(st.integers())
11+
@pytest.mark.asyncio
12+
async def test_mark_inner(n):
13+
assert isinstance(n, int)
14+
15+
16+
@pytest.mark.asyncio
17+
@given(st.integers())
18+
async def test_mark_outer(n):
19+
assert isinstance(n, int)
20+
21+
22+
@pytest.mark.parametrize("y", [1, 2])
23+
@given(x=st.none())
24+
@pytest.mark.asyncio
25+
async def test_mark_and_parametrize(x, y):
26+
assert x is None
27+
assert y in (1, 2)

0 commit comments

Comments
 (0)