From db90668cc1e3937a5321f87e97466b888678abca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 09:22:32 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pytest_bdd/scenario.py | 21 +++++++++++++---- src/pytest_bdd/steps.py | 8 ++++++- tests/feature/test_steps.py | 46 +++++++++++++++++++++++++++++++++++++ tests/steps/test_given.py | 33 ++++++++++++++++---------- 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/pytest_bdd/scenario.py b/src/pytest_bdd/scenario.py index 864346ce..30a9b97e 100644 --- a/src/pytest_bdd/scenario.py +++ b/src/pytest_bdd/scenario.py @@ -162,11 +162,22 @@ def _execute_step_function( raise if context.target_fixture is not None: - target_fixture_tokens = [token for token in context.target_fixture.split(',') if token] - return_values = (return_value,) if not isinstance(return_value, tuple) else return_value - assert len(target_fixture_tokens) == len(return_values), f"Return value count: {len(return_values)} are not matching target_fixture count: {len(target_fixture_tokens)}" - for token, value in zip(target_fixture_tokens, return_values): - inject_fixture(request, token, value) + if isinstance(return_value, Exception): + arg = context.target_exception if context.target_exception else "response" + inject_fixture(request=request, arg=arg, value=return_value) + else: + target_fixture_tokens = [token for token in context.target_fixture.split(",") if token] + # Single return value in target_fixture + if len(target_fixture_tokens) == 1: + inject_fixture(request=request, arg=target_fixture_tokens[0], value=return_value) + # Multiple comma separated return values in target_fixture + else: + return_values = (return_value,) if not isinstance(return_value, tuple) else return_value + assert len(target_fixture_tokens) == len( + return_values + ), f"Return value count: {len(return_values)} are not matching target_fixture count: {len(target_fixture_tokens)}" + for token, value in zip(target_fixture_tokens, return_values): + inject_fixture(request=request, arg=token, value=value) request.config.hook.pytest_bdd_after_step(**kw) diff --git a/src/pytest_bdd/steps.py b/src/pytest_bdd/steps.py index 5ed3529d..b29cb419 100644 --- a/src/pytest_bdd/steps.py +++ b/src/pytest_bdd/steps.py @@ -66,6 +66,7 @@ class StepFunctionContext: parser: StepParser converters: dict[str, Callable[..., Any]] = field(default_factory=dict) target_fixture: str | None = None + target_exception: str | None = None def get_step_fixture_name(step: Step) -> str: @@ -96,6 +97,7 @@ def when( name: str | StepParser, converters: dict[str, Callable] | None = None, target_fixture: str | None = None, + target_exception: str | None = None, stacklevel: int = 1, ) -> Callable: """When step decorator. @@ -104,11 +106,12 @@ def when( :param converters: Optional `dict` of the argument or parameter converters in form {: }. :param target_fixture: Target fixture name to replace by steps definition function. + :param target_exception: Target exception name to receive Exception object :param stacklevel: Stack level to find the caller frame. This is used when injecting the step definition fixture. :return: Decorator function for the step. """ - return step(name, WHEN, converters=converters, target_fixture=target_fixture, stacklevel=stacklevel) + return step(name, WHEN, converters=converters, target_fixture=target_fixture, target_exception=target_exception, stacklevel=stacklevel) def then( @@ -135,6 +138,7 @@ def step( type_: Literal["given", "when", "then"] | None = None, converters: dict[str, Callable] | None = None, target_fixture: str | None = None, + target_exception: str | None = None, stacklevel: int = 1, ) -> Callable[[TCallable], TCallable]: """Generic step decorator. @@ -143,6 +147,7 @@ def step( :param type_: Step type ("given", "when" or "then"). If None, this step will work for all the types. :param converters: Optional step arguments converters mapping. :param target_fixture: Optional fixture name to replace by step definition. + :param target_exception: Optional target exception name :param stacklevel: Stack level to find the caller frame. This is used when injecting the step definition fixture. :return: Decorator function for the step. @@ -165,6 +170,7 @@ def decorator(func: TCallable) -> TCallable: parser=parser, converters=converters, target_fixture=target_fixture, + target_exception=target_exception, ) def step_function_marker() -> StepFunctionContext: diff --git a/tests/feature/test_steps.py b/tests/feature/test_steps.py index 30b731c0..343cff2d 100644 --- a/tests/feature/test_steps.py +++ b/tests/feature/test_steps.py @@ -584,3 +584,49 @@ def _(stuff): "*Tearing down...*", ] ) + + +def test_when_exception(pytester): + pytester.makefile( + ".feature", + when_exception=textwrap.dedent( + """\ + Feature: When exception + Scenario: Test when exception is generated + When I have injected exception + Then Exception object should be received as default 'response' param + When I have injected exception with target_exception='exception_ret' + Then Exception object should be received as target_exception='exception_ret' + """ + ), + ) + pytester.makepyfile( + textwrap.dedent( + """\ + import pytest + from pytest_bdd import when, then, scenario + + @scenario("when_exception.feature", "Test when exception is generated") + def test_when_exception(): + pass + + @when("I have injected exception", target_fixture="foo") + def _(): + return Exception("Dummy Exception obj") + + @when("I have injected exception with target_exception='exception_ret'", target_fixture="foo", target_exception="exception_ret") + def _(): + return Exception("Dummy Exception obj") + + @then("Exception object should be received as default 'response' param") + def _(response): + assert isinstance(response, Exception), "response is not Exception object" + + @then("Exception object should be received as target_exception='exception_ret'") + def _(exception_ret): + assert isinstance(exception_ret, Exception), "response is not Exception object" + """ + ) + ) + result = pytester.runpytest() + result.assert_outcomes(passed=1) diff --git a/tests/steps/test_given.py b/tests/steps/test_given.py index caa890b9..b76b37b2 100644 --- a/tests/steps/test_given.py +++ b/tests/steps/test_given.py @@ -2,15 +2,17 @@ import textwrap -def test_given_injection(pytester): +def test_given_injection_single_value(pytester): pytester.makefile( ".feature", given=textwrap.dedent( """\ Feature: Given Scenario: Test given fixture injection - Given I have injecting given + Given I have injected single value Then foo should be "injected foo" + Given I have injected tuple value + Then foo should be tuple value """ ), ) @@ -24,15 +26,21 @@ def test_given_injection(pytester): def test_given(): pass - @given("I have injecting given", target_fixture="foo") + @given("I have injected single value", target_fixture="foo") def _(): return "injected foo" + @given("I have injected tuple value", target_fixture="foo") + def _(): + return ("injected foo", {"city": ["Boston", "Houston"]}, [10,20,30],) @then('foo should be "injected foo"') def _(foo): assert foo == "injected foo" + @then('foo should be tuple value') + def _(foo): + assert foo == ("injected foo", {"city": ["Boston", "Houston"]}, [10,20,30],) """ ) ) @@ -40,15 +48,15 @@ def _(foo): result.assert_outcomes(passed=1) -def test_given_injection_multiple(pytester): +def test_given_injection_multiple_values(pytester): pytester.makefile( ".feature", given=textwrap.dedent( """\ Feature: Given Scenario: Test given fixture injection - Given I have injecting given - Then foo should be "injected foo" + Given I have injecting given values + Then values should be received """ ), ) @@ -62,18 +70,19 @@ def test_given_injection_multiple(pytester): def test_given(): pass - @given("I have injecting given", target_fixture="foo,bar") + @given("I have injecting given values", target_fixture="foo,city,numbers") def _(): - return "injected foo", "injected bar" + return ("injected foo", {"city": ["Boston", "Houston"]}, [10,20,30],) - @then('foo should be "injected foo"') - def _(foo, bar): + @then("values should be received") + def _(foo, city, numbers): assert foo == "injected foo" - assert bar == "injected bar" + assert city == {"city": ["Boston", "Houston"]} + assert numbers == [10,20,30] """ ) ) result = pytester.runpytest() - result.assert_outcomes(passed=1) \ No newline at end of file + result.assert_outcomes(passed=1)