Bare async step implementation

This commit is contained in:
Alessio Bogon 2023-07-23 23:48:35 +02:00
parent 0fab820f53
commit d12403e469
4 changed files with 106 additions and 2 deletions

20
poetry.lock generated
View File

@ -425,6 +425,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.21.1"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"},
{file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"},
]
[package.dependencies]
pytest = ">=7.0.0"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
[[package]]
name = "pytest-xdist"
version = "3.3.1"
@ -553,4 +571,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata]
lock-version = "2.0"
python-versions = ">=3.8"
content-hash = "b2ba37df5fea186a1726b64f9c426661c8b228b3b6f9954eeba805e7653a087c"
content-hash = "a060ad36b10706adf9d533433154ce45a0282675dbb9d9873cf3162a152cb8e1"

View File

@ -48,6 +48,7 @@ types-setuptools = "^65.5.0.2"
pytest-xdist = "^3.0.2"
coverage = {extras = ["toml"], version = "^6.5.0"}
Pygments = "^2.13.0" # for code-block highlighting
pytest-asyncio = "^0.21.1"
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -12,7 +12,9 @@ test_publish_article = scenario(
"""
from __future__ import annotations
import asyncio
import contextlib
import functools
import logging
import os
import re
@ -120,6 +122,19 @@ def get_step_function(request, step: Step) -> StepFunctionContext | None:
return None
def ensure_sync(fn):
"""Convert async function to sync function."""
__tracebackhide__ = True
if not asyncio.iscoroutinefunction(fn):
return fn
@functools.wraps(fn)
def wrapper(*args, **kwargs):
return asyncio.run(fn(*args, **kwargs))
return wrapper
def _execute_step_function(
request: FixtureRequest, scenario: Scenario, step: Step, context: StepFunctionContext
) -> None:
@ -156,7 +171,8 @@ def _execute_step_function(
request.config.hook.pytest_bdd_before_step_call(**kw)
# Execute the step as if it was a pytest fixture, so that we can allow "yield" statements in it
return_value = call_fixture_func(fixturefunc=context.step_func, request=request, kwargs=kwargs)
step_func = ensure_sync(context.step_func)
return_value = call_fixture_func(fixturefunc=step_func, request=request, kwargs=kwargs)
except Exception as exception:
request.config.hook.pytest_bdd_step_error(exception=exception, **kw)
raise

View File

@ -0,0 +1,69 @@
import textwrap
# TODO: Split this test in one that checks that we work correctly
# with the pytest-asyncio plugin, and another that checks that we correctly
# run async steps.
def test_async_steps(pytester):
"""Test parent given is collected.
Both fixtures come from the parent conftest.
"""
pytester.makefile(
".feature",
async_feature=textwrap.dedent(
"""\
Feature: A feature
Scenario: A scenario
Given There is an async object
When I do an async action
Then the async object value should be "async_object"
And [async] the async object value should be "async_object"
And the another async object value should be "another_async_object"
"""
),
)
pytester.makepyfile(
textwrap.dedent(
"""\
from pytest_bdd import given, parsers, scenarios, then, when
import asyncio
import pytest
scenarios("async_feature.feature")
@pytest.fixture
async def another_async_object():
await asyncio.sleep(0.01)
return "another_async_object"
@given("There is an async object", target_fixture="async_object")
async def given_async_obj():
await asyncio.sleep(0.01)
return "async_object"
@when("I do an async action")
async def when_i_do_async_action():
await asyncio.sleep(0.01)
@then(parsers.parse('the async object value should be "{value}"'))
async def the_sync_object_value_should_be(async_object, value):
assert async_object == value
@then(parsers.parse('[async] the async object value should be "{value}"'))
async def async_the_async_object_value_should_be(async_object, value):
await asyncio.sleep(0.01)
assert async_object == value
@then(parsers.parse('the another async object value should be "{value}"'))
def the_another_async_object_value_should_be(another_async_object, value):
assert another_async_object == value
"""
)
)
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)