pythonpytestpytest-bdd

Pytest BDD - Problem with Fixtures and Conftest


I'm trying to automatize API testing with pytest-bdd and gherkin feature files.

My folder structur looks like this

C:.
│   config.json
│   conftest.py
│   geckodriver.log
│   __init__.py

├───.vscode
│       settings.json
│
├───features
│       api.feature
│
├───step_defs
│   │   test_apis.py
│   │   __init__.py
 

I have following feature file:

api.feature

Feature: API Tests
As a User,
I want to have feedback from external APIs,
So that my correct user manual can be found.
    
Background:
    Given The user is authenticated on stage "var_stage" with tenant "var_tenant"

Scenario: Basic Api request
    Given I set the request to endpoint "var_endpoint"
    When I send a HTTP "get" request
    Then I expect http response with status code "var_status_code" and response type "json"

I want to test different APIs but want to ensure that the necessary authentication is done once before (see Brackground) different scenarios are executed. For this reason I have setup the authentication using session of the user for the stage and tenant specified in the feature file in conftest.py together with a fixture.

    conftest.py

    import pytest
    import requests
    from pytest_bdd import given, parsers
    
    
    @pytest.fixture
    def config(scope='session'):
    
        # Read the file
        with open('config.json') as config_file:
            config = json.load(config_file)
    
        # Return config so it can be used
        return config

    @pytest.fixture
    @given(parsers.parse('The user is authenticated on stage "{stage}" with tenant "{tenant}"'))
    def http_session(config, stage, tenant, scope = 'session'):
    
        login_url = "https://"+stage+"/"+tenant+"/public/login"
    
        payload = {
           'username': config['username'],
           'password': config['password']
        }
    
        session = requests.Session()
    
        response = session.post(login_url, data=payload, allow_redirects=True)

My other file looks like this:

    test_apis.py
    
    from pytest_bdd import scenarios, given, when, then, parsers
    
    scenarios('../features/api.feature')
    
    @given(parsers.parse('I set the request to endpoint "{endpoint}"'))
    def set_endpoint(config, endpoint):
    
        assert len(endpoint) > 0
    
        url = config['url'] + \
            endpoint
        config['url'] = url
    
    
    @when(parsers.parse('I send a HTTP "{http_type}" request'))
    def set_http_type(config, http_type):
    
        assert len(http_type) > 0
    
        config['http_type'] = http_type
    
    
    @then(parsers.parse('I expect http response with status code "{status_code}" and response type "{response_type}"'))
    def request_endpoint_statuscode(config, http_session,  status_code, response_type):
    
        assert len(status_code) > 0
        assert len(response_type) > 0
        
        headers = config['headers']
        url = config['url']
        http_type = config['http_type']
        status_code_respone = None
    
        if http_type == 'get':
            status_code_response = http_session.get(
                url, headers=headers, allow_redirects=True)
    
        elif http_type == 'post':
            status_code_response = http_session.post(
                url, headers=headers, allow_redirects=True)
    
        assert status_code_response == int(status_code)

Running this I receive following error

    @pytest.fixture
      @given(parsers.parse('The user is authenticated on stage "{stage}" with tenant "{tenant}"'))
      def http_session(config, stage, tenant, scope='session'):
    E       fixture 'stage' not found
    >       available fixtures: _pytest_bdd_example, browser, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, config, doctest_namespace, http_session, monkeypatch, pytestbdd_stepdef_given_I set brandcode to "{brandcode}", pytestbdd_stepdef_given_I set the request to endpoint "{endpoint}", pytestbdd_stepdef_given_The user is authenticated on stage "{stage}" with tenant "{tenant}", pytestbdd_stepdef_given_trace, pytestbdd_stepdef_then_I expect http response with status code "{status_code}" and response type "{response_type}", pytestbdd_stepdef_then_response content length shall be greater than "{length}", pytestbdd_stepdef_then_trace, pytestbdd_stepdef_when_I send a HTTP "{http_type}" request, pytestbdd_stepdef_when_trace, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
    >       use 'pytest --fixtures [testpath]' for help on them.

I guess that it has something to do with the feature file and how it is used together with pytest.fixtures. Using only configuration file without pytest-bdd in conftest worked fine for creation of the http session. However I want to be able to specify the stage and tenant (and possibly also credentials for a test user) within the feature file.


Solution

  • The error occurs because pytest tries to run http_session as a fixtures but cannot find the required parameters stage and tenant as fixtures before pytest-bdd even executes the step Given The user is authenticated on stage "var_stage" with tenant "var_tenant".

    If you want to use a pytest-bdd step as fixture in subsequent steps, you have to adjust the @scenario decorator as described in the docs; remove the @fixture part and add target_fixture="<fixture-name>" to @scenario.

    So your http_session function would look like the following after the adjustment:

    @given(parsers.parse('The user is authenticated on stage "{stage}" with tenant "{tenant}"'), target_fixture="http_session")
    def http_session(config, stage, tenant, scope = 'session'):
        # ...