Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Q: why '@when' doesn't export target_fixture? #402

Closed
bulletRush opened this issue Feb 5, 2021 · 9 comments · Fixed by #431
Closed

Q: why '@when' doesn't export target_fixture? #402

bulletRush opened this issue Feb 5, 2021 · 9 comments · Fixed by #431

Comments

@bulletRush
Copy link

bulletRush commented Feb 5, 2021

I am a newbie of pytest-bdd. I have a question about when decorator.
If we export 'target_fixture' args for 'when' decorator, we can rewrite

        @when('I eat <eat> cucumbers')
        def eat_cucumbers(start_cucumbers, eat):
            assert isinstance(eat, float)
            start_cucumbers['eat'] = eat


        @then('I should have <left> cucumbers')
        def should_have_left_cucumbers(start_cucumbers, start, eat, left):
            assert isinstance(left, str)
            assert start - eat == int(left)
            assert start_cucumbers['start'] == start
            assert start_cucumbers['eat'] == eat

to

        @when('I eat <eat> cucumbers', target_fixture='left')
        def eat_cucumbers(start, eat):
            assert isinstance(eat, float)
            left = start - eat
            return left


        @then('I should have <expect_left> cucumbers')
        def should_have_left_cucumbers(expect_left, left):
            assert expect_left == left

for real project test cases, this will make more easy for reusing those steps.
so why pytest-bdd does not export target_fixture for when decorator? should I make a MR for this change?

@nileshpandey3
Copy link

nileshpandey3 commented Feb 26, 2021

@bulletRush

@mattelacchiato
Copy link

mattelacchiato commented Apr 20, 2021

I've just struggled with the same issue. Since there doesn't seem to be an argument against an implementation, at least I would be very thankful for a MR @bulletRush ❤️

@youtux
Copy link
Contributor

youtux commented Apr 20, 2021

target_fixture is only available for "given" steps, and that's by design.

If you need the result of your step to be used somewhere else, then your step should probably be a "given".
"when" steps are supposed to execute actions, and this design encourages you to think which part of your scenario is part of the setup ("given" steps) vs what is the exercise ("when" steps) vs what is the assertion ("then" steps).

Although I wouldn't recommend it, if you really need to store the result of a step and reuse it somewhere else, you can always use a fixture like

@pytest.fixture
def context():
    return {}

and use its content in the "when" and "then" steps:

@when("something happens")
def when_something_happens(context):
    context['something_happened'] = True

@then("something should have happened")
def then_something_has_happened(context):
    assert context['something_happened'] is True

A PR that allows "when" steps to have a target_fixture parameter would probably be rejected unless there is a very good use case and motivation for it.

@1Mark
Copy link

1Mark commented Apr 24, 2021

@youtux the issue here is that pytest-bdd assumes you are not testing the return values of functions. The action in the when step can and often is a function that has a return value. How can you assert the return value in the then step?

Given I want to search for cake
When I send a GET request to https://google.co.uk with cake as the payload
Then I get a response with the corresponding HTML of the page

How would you test this? You need a way to cleanly pass the response from when to then.

Otherwise, people will be forced to skip when and just go straight use another given.

@youtux
Copy link
Contributor

youtux commented Jun 27, 2021

@pytest.fixture
def context():
    return {}

@given("I want to search for cake")
def i_want_to_search_for_cake(...):
    ...

@when(parsers.parse("I send a GET request to {url} with cake as the payload"))
def i_send_request_with_cacke_as_payload(context, http_client, method, url):
    context["response"] = http_client.get(url, {"cake": "a cake"})

@then("I get a response with the corresponding HTML of the page")
def i_get_a_response_with_html(context):
    html = parse_html(context["response"].content)
    assert html.title = "a cake"
    assert ...

@youtux youtux closed this as completed Jun 27, 2021
@1Mark
Copy link

1Mark commented Jun 27, 2021

This is just a dirty workaround. Users can then just put anything inside of context. Also the name context isn't very useful. I don't see why you're against such a change. There is a genuine use case and instead of supporting it you just want everyone to use a workaround.

:(

@youtux
Copy link
Contributor

youtux commented Jun 28, 2021

Indeed this is a use case, maybe the result of a when/then step can be injected as a fixture in the context, so that assertions can refer to it more naturally.

@olegpidsadnyi what do you think about allowing when/then steps to have a target_fixture param to store the result of the step? Maybe one more motivation for it is that you could have "then" statements that can assert on an object that is created by the setup, or as a result of a user interaction.

For example:

Feature: Blog

    Scenario: Publishing the article
        Given there is an article

        When I go to the article page
        And I press the publish button

        Then the article should be published

    Scenario: Creating the article
        When I go to the create article page
        And I press the create button

        Then the article should not be published

and the steps could be defined like

from pytest_bdd import scenario, given, when, then

scenarios(...)

@given("there is an article", target_fixture="article")
def there_is_an_article():
    return create_test_article()

...

@then("there should be a new article")
def there_should_be_a_new_article(session, target_fixture="article"):  # <-- Important bit here
    article session.query(Article).order_by(Article.id.desc()).first()
    assert article is not None
    return article

@then(parsers.re(r"the article should (?P<negation>not )be published")
def article_is_published(article, negation):  # <-- this article comes either form the Given step or the Then step
    expected = not negation
    assert article.is_published is expected

@youtux youtux reopened this Jun 28, 2021
@1Mark
Copy link

1Mark commented Jul 2, 2021

@olegpidsadnyi please tell me you agree with me

@olegpidsadnyi
Copy link
Contributor

@1Mark @youtux I've never needed that before since we were using splinter with the common browser fixture. Or the database. That was the common context. I can't think of a harm if we make the target_fixture similar to the way the parsed step params are working. Let's go for it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants