Skip to content

Commit d527a67

Browse files
authored
Merge pull request #737 from yunxuo/master
fix: default value not covered by parameters passed through feature file
2 parents 4602314 + afec8b1 commit d527a67

File tree

3 files changed

+55
-12
lines changed

3 files changed

+55
-12
lines changed

src/pytest_bdd/scenario.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import re
2020
from collections.abc import Iterable, Iterator
21+
from inspect import signature
2122
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
2223

2324
import pytest
@@ -28,7 +29,7 @@
2829
from .compat import getfixturedefs, inject_fixture
2930
from .feature import get_feature, get_features
3031
from .steps import StepFunctionContext, get_step_fixture_name
31-
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path, identity
32+
from .utils import CONFIG_STACK, get_caller_module_locals, get_caller_module_path, get_required_args, identity
3233

3334
if TYPE_CHECKING:
3435
from _pytest.mark.structures import ParameterSet
@@ -201,6 +202,9 @@ def _execute_step_function(
201202
) -> None:
202203
"""Execute step function."""
203204
__tracebackhide__ = True
205+
206+
func_sig = signature(context.step_func)
207+
204208
kw = {
205209
"request": request,
206210
"feature": scenario.feature,
@@ -210,24 +214,31 @@ def _execute_step_function(
210214
"step_func_args": {},
211215
}
212216
request.config.hook.pytest_bdd_before_step(**kw)
213-
args = get_args(context.step_func)
214217

215218
try:
216-
kwargs = parse_step_arguments(step=step, context=context)
219+
parsed_args = parse_step_arguments(step=step, context=context)
217220

218-
if step.datatable is not None:
219-
kwargs[STEP_ARGUMENT_DATATABLE] = step.datatable.raw()
221+
# Filter out the arguments that are not in the function signature
222+
kwargs = {k: v for k, v in parsed_args.items() if k in func_sig.parameters}
220223

221-
if step.docstring is not None:
224+
if STEP_ARGUMENT_DATATABLE in func_sig.parameters and step.datatable is not None:
225+
kwargs[STEP_ARGUMENT_DATATABLE] = step.datatable.raw()
226+
if STEP_ARGUMENT_DOCSTRING in func_sig.parameters and step.docstring is not None:
222227
kwargs[STEP_ARGUMENT_DOCSTRING] = step.docstring
223228

224-
kwargs = {arg: kwargs[arg] if arg in kwargs else request.getfixturevalue(arg) for arg in args}
229+
# Fill the missing arguments requesting the fixture values
230+
kwargs |= {
231+
arg: request.getfixturevalue(arg) for arg in get_required_args(context.step_func) if arg not in kwargs
232+
}
225233

226234
kw["step_func_args"] = kwargs
227235

228236
request.config.hook.pytest_bdd_before_step_call(**kw)
229-
# Execute the step as if it was a pytest fixture, so that we can allow "yield" statements in it
237+
238+
# Execute the step as if it was a pytest fixture using `call_fixture_func`,
239+
# so that we can allow "yield" statements in it
230240
return_value = call_fixture_func(fixturefunc=context.step_func, request=request, kwargs=kwargs)
241+
231242
except Exception as exception:
232243
request.config.hook.pytest_bdd_step_error(exception=exception, **kw)
233244
raise
@@ -280,7 +291,7 @@ def decorator(*args: Callable[P, T]) -> Callable[P, T]:
280291
"scenario function can only be used as a decorator. Refer to the documentation."
281292
)
282293
[fn] = args
283-
func_args = get_args(fn)
294+
func_args = get_required_args(fn)
284295

285296
def scenario_wrapper(request: FixtureRequest, _pytest_bdd_example: dict[str, str]) -> Any:
286297
__tracebackhide__ = True

src/pytest_bdd/utils.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@
2020
CONFIG_STACK: list[Config] = []
2121

2222

23-
def get_args(func: Callable[..., Any]) -> list[str]:
24-
"""Get a list of argument names for a function.
23+
def get_required_args(func: Callable[..., Any]) -> list[str]:
24+
"""Get a list of argument that are required for a function.
2525
2626
:param func: The function to inspect.
2727
2828
:return: A list of argument names.
29-
:rtype: list
3029
"""
3130
params = signature(func).parameters.values()
3231
return [

tests/feature/test_scenario.py

+33
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,36 @@ def _():
393393
("given", "che uso uno step con ", "esempio 2"),
394394
("then", "va tutto bene"),
395395
]
396+
397+
398+
def test_default_value_is_used_as_fallback(pytester):
399+
"""Test that the default value for a step implementation is only used as a fallback."""
400+
pytester.makefile(
401+
".feature",
402+
simple="""
403+
Feature: Simple feature
404+
Scenario: Step using default arg
405+
Given a user with default username
406+
407+
Scenario: Step using explicit value
408+
Given a user with username "user1"
409+
""",
410+
)
411+
pytester.makepyfile(
412+
"""
413+
from pytest_bdd import scenarios, given, then, parsers
414+
from pytest_bdd.utils import dump_obj
415+
416+
scenarios("simple.feature")
417+
418+
@given('a user with default username', target_fixture="user")
419+
@given(parsers.parse('a user with username "{username}"'), target_fixture="user")
420+
def create_user(username="defaultuser"):
421+
dump_obj(username)
422+
423+
"""
424+
)
425+
result = pytester.runpytest("-s")
426+
result.assert_outcomes(passed=2)
427+
428+
assert collect_dumped_objects(result) == ["defaultuser", "user1"]

0 commit comments

Comments
 (0)