forked from test_framework/pytest-bdd
Fix bug when 2 steps have the same step name...
... but only one step impl was kept.
This commit is contained in:
parent
cc415d3d1c
commit
63a4268b9c
|
@ -48,7 +48,7 @@ def iter_argumented_step_function(
|
|||
"""Iterate over argumented step functions."""
|
||||
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
|
||||
fixture_def_by_name = list(fixturemanager._arg2fixturedefs.items())
|
||||
for i, (fixturename, fixturedefs) in enumerate(reversed(fixture_def_by_name)):
|
||||
for i, (fixturename, fixturedefs) in enumerate(fixture_def_by_name):
|
||||
for pos, fixturedef in enumerate(fixturedefs):
|
||||
step_func_context = getattr(fixturedef.func, "_pytest_bdd_step_context", None)
|
||||
if step_func_context is None:
|
||||
|
|
|
@ -37,7 +37,8 @@ def _(article):
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, TypeVar
|
||||
from itertools import count
|
||||
from typing import Any, Callable, Iterable, TypeVar
|
||||
|
||||
import pytest
|
||||
from _pytest.fixtures import FixtureDef, FixtureRequest
|
||||
|
@ -52,7 +53,6 @@ TCallable = TypeVar("TCallable", bound=Callable[..., Any])
|
|||
|
||||
@dataclass
|
||||
class StepFunctionContext:
|
||||
name: str
|
||||
type: Literal["given", "when", "then"]
|
||||
step_func: Callable[..., Any]
|
||||
parser: StepParser
|
||||
|
@ -60,17 +60,6 @@ class StepFunctionContext:
|
|||
target_fixture: str | None = None
|
||||
|
||||
|
||||
def get_step_fixture_name(name: str, type_: str) -> str:
|
||||
"""Get step fixture name.
|
||||
|
||||
:param name: string
|
||||
:param type: step type
|
||||
:return: step fixture name
|
||||
:rtype: string
|
||||
"""
|
||||
return f"pytestbdd_{type_}_{name}"
|
||||
|
||||
|
||||
def get_parsed_step_fixture_name(name: str, type_: str) -> str:
|
||||
"""Get step fixture name.
|
||||
|
||||
|
@ -129,6 +118,23 @@ def then(
|
|||
return _step_decorator(THEN, name, converters=converters, target_fixture=target_fixture)
|
||||
|
||||
|
||||
def find_unique_name(name: str, seen: Iterable[str]) -> str:
|
||||
"""Find unique name.
|
||||
|
||||
:param name: string
|
||||
:param seen: iterable of strings
|
||||
:return: unique string
|
||||
"""
|
||||
seen = set(seen)
|
||||
if name not in seen:
|
||||
return name
|
||||
|
||||
for i in count(1):
|
||||
new_name = f"{name}_{i}"
|
||||
if new_name not in seen:
|
||||
return new_name
|
||||
|
||||
|
||||
def _step_decorator(
|
||||
step_type: Literal["given", "when", "then"],
|
||||
step_name: str | StepParser,
|
||||
|
@ -149,12 +155,8 @@ def _step_decorator(
|
|||
|
||||
def decorator(func: TCallable) -> TCallable:
|
||||
parser = get_parser(step_name)
|
||||
parsed_step_name = parser.name
|
||||
|
||||
fixture_step_name = get_step_fixture_name(parsed_step_name, step_type)
|
||||
|
||||
context = StepFunctionContext(
|
||||
name=fixture_step_name,
|
||||
type=step_type,
|
||||
step_func=func,
|
||||
parser=parser,
|
||||
|
@ -162,13 +164,13 @@ def _step_decorator(
|
|||
target_fixture=target_fixture,
|
||||
)
|
||||
|
||||
# TODO: Probably we can keep on returning None here instead
|
||||
def step_function_marker() -> StepFunctionContext:
|
||||
return context
|
||||
|
||||
step_function_marker._pytest_bdd_step_context = context
|
||||
|
||||
caller_locals = get_caller_module_locals()
|
||||
fixture_step_name = find_unique_name(f"pytestbdd_stepdef_{step_type}_{parser.name}", seen=caller_locals.keys())
|
||||
caller_locals[fixture_step_name] = pytest.fixture(name=fixture_step_name)(step_function_marker)
|
||||
return func
|
||||
|
||||
|
|
|
@ -49,3 +49,51 @@ def test_step_function_multiple_target_fixtures(testdir):
|
|||
[foo, bar] = collect_dumped_objects(result)
|
||||
assert foo == "test foo"
|
||||
assert bar == "test bar"
|
||||
|
||||
|
||||
def test_step_functions_same_parser(testdir):
|
||||
testdir.makefile(
|
||||
".feature",
|
||||
target_fixture=textwrap.dedent(
|
||||
"""\
|
||||
Feature: A feature
|
||||
Scenario: A scenario
|
||||
Given there is a foo with value "(?P<value>\\w+)"
|
||||
And there is a foo with value "testfoo"
|
||||
When pass
|
||||
Then pass
|
||||
"""
|
||||
),
|
||||
)
|
||||
testdir.makepyfile(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
from pytest_bdd import given, when, then, scenarios, parsers
|
||||
from pytest_bdd.utils import dump_obj
|
||||
|
||||
scenarios("target_fixture.feature")
|
||||
|
||||
STEP = 'there is a foo with value "(?P<value>\\w+)"'
|
||||
|
||||
@given(STEP)
|
||||
def _():
|
||||
dump_obj(('str',))
|
||||
|
||||
@given(parsers.re(STEP))
|
||||
def _(value):
|
||||
dump_obj(('re', value))
|
||||
|
||||
@when("pass")
|
||||
@then("pass")
|
||||
def _():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
)
|
||||
result = testdir.runpytest("-s")
|
||||
result.assert_outcomes(passed=1)
|
||||
|
||||
[first_given, second_given] = collect_dumped_objects(result)
|
||||
assert first_given == ("str",)
|
||||
assert second_given == ("re", "testfoo")
|
||||
|
|
Loading…
Reference in New Issue