Skip to content

Commit f366ee9

Browse files
authored
Merge pull request #102 from Zac-HD/hypothesis
Support async tests which use Hypothesis
2 parents 923fe9a + 1b66314 commit f366ee9

File tree

5 files changed

+67
-2
lines changed

5 files changed

+67
-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

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 marker_name in pyfuncitem.keywords \
144+
and not getattr(pyfuncitem.obj, 'is_hypothesis_test', False):
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

setup.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def find_version():
4343
install_requires=["pytest >= 3.0.6"],
4444
extras_require={
4545
':python_version == "3.5"': "async_generator >= 1.3",
46-
"testing": ["coverage", "async_generator >= 1.3"],
46+
"testing": [
47+
"coverage",
48+
"async_generator >= 1.3",
49+
"hypothesis >= 3.64",
50+
],
4751
},
4852
entry_points={"pytest11": ["asyncio = pytest_asyncio.plugin"]},
4953
)

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)