key error in requests object, even though key is present (pytest-bdd)

1.4k views Asked by At

I have the following feature in tests/features/Admin.feature

Feature: Admin

  Scenario Outline: register a new user
    Given I'm logged in as an admin at "<host_name>" with email "<admin_email>" and password "<admin_password>"
    When I call the register method for host "<host_name>" with email "<user_email>" and password "<user_password>" and firstName "<first_name>" and last name "<last_name>"
    Then the user will be able to log in to "<host_name>"

      | host_name      | admin_email          | admin_password | user_email                           | user_password | first_name | last_name |
      | localhost:3000 | [email protected] | somepassword   | [email protected] | somepassword  | john       | jjjjjjj   |

and it is being tested by the following:

import pytest
from pytest_bdd import scenario, given, when, then, parsers
from admin import Admin
from user import User

@scenario('../features/Admin.feature',
          'register a new user')
def test_admin():
    pass

@pytest.fixture
@given('I\'m logged in as an admin at "<host_name>" with email "<admin_email>" and password "<admin_password>"')
def admin_login(host_name, admin_email, admin_password):
    admin = Admin(admin_email, admin_password)
    admin.login(host_name)
    assert admin.status_code == 200
    return admin

@pytest.fixture
@when('I call the register method for host "<host_name>" with email "<user_email>" and password "<user_password>" and firstName "<first_name>" and last name "<last_name>"')
def register_user(admin_login, host_name, user_email, user_password, first_name, last_name):
    user_id = admin_login.register_user(host_name, user_email, user_password, first_name, last_name)
    print('the user id is: ' + user_id)
    assert admin_login.status_code == 200
    user = User(user_email, user_password, _id=user_id)
    print(user)
    return user

@then('the user will be able to log in to "<host_name>"')
def user_login(host_name):
   user.login(host_name)
   assert user.status_code == 200
   return user

Somehow, it appears the admin.register_user method is being called twice. I don't know any other way to explain the key error. On the second time, since the user should already be registered, I would expect to get an error saying the user is already registered. This is what I get, even though I've only meant to register once. Here's the code that does the registering:

import requests
import json
from user import User

def get_login_url(host):
    return f'http://{host}/api/auth/login'

def get_register_url(host):
    return f'http://{host}/api/auth/register'

class Admin:
    def __init__(self, email, password):
        self.email = email
        self.password = password
        self.bearer_token = None
        self.status_code = None  # used to check status codes

    def register_user(self, host, email, password, first_name, last_name):
        # make sure bearer is present
        if not len(self.bearer_token) > 0:
            raise Exception('Must log in before registering users.')
        # send registration request
        registration_headers = {"Authorization": f'BEARER {self.bearer_token}', 'content-type': 'application/json', 'cookie': 'auth.strategy=local', 'auth._token.local': "false"}
        registration_credentials = f'"email": "{email}", "password": "{password}", "firstName": "{first_name}", "lastName": "{last_name}"'
        registration_payload = "{" + registration_credentials + "}"
        registration_request = requests.post(get_register_url(host), data=registration_payload, headers=registration_headers)
        user_id = json.loads(registration_request.text)['user']['_id']
        print(json.loads(registration_request.text).keys())
        return user_id

So when I run pytest, I get an error message, with part of it reading:

self = <admin.Admin object at 0x10ad47d90>, host = 'localhost:3000'
email = '[email protected]', password = 'somepassword'
first_name = 'john', last_name = 'jjjjjjj'

    def register_user(self, host, email, password, first_name, last_name):
        # make sure bearer is present
        if not len(self.bearer_token) > 0:
            raise Exception('Must log in before registering users.')
        # send registration request
        registration_headers = {"Authorization": f'BEARER {self.bearer_token}', 'content-type': 'application/json', 'cookie': 'auth.strategy=local', 'auth._token.local': "false"}
        registration_credentials = f'"email": "{email}", "password": "{password}", "firstName": "{first_name}", "lastName": "{last_name}"'
        registration_payload = "{" + registration_credentials + "}"
        registration_request = requests.post(get_register_url(host), data=registration_payload, headers=registration_headers)
>       user_id = json.loads(registration_request.text)['user']['_id']
E       KeyError: 'user'

admin.py:27: KeyError
----------------------------- Captured stdout call -----------------------------
dict_keys(['success', 'status', 'user'])
the user id is: 5fbfa5c6f45373b57d7dad0f
<user.User object at 0x10a3442d0>
=========================== short test summary info ============================
FAILED tests/step_defs/test_admin.py::test_admin[localhost:3000-admin_user@gmail.com-somepassword-nowaythisusernameistaken18@gmail.com-somepassword-john-jjjjjjj]
============================== 1 failed in 1.69s ===============================

How can the stdout have captured the user id, if the user key is not present in the response?

1

There are 1 answers

0
mattefrank On

I would assume the issue is coming from using @pytest.fixture in the given and when steps, because both steps will then be executed from pytest independently to create a fixture and from pytest-bdd within the scenario context.

Solution: Remove the @pytest.fixture annotations from admin_login and register_user steps.

If you want a fixture to be associated with a pytest-bdd step you can do it in the following two ways:

  1. Create a fixture and "use" it in the BDD step: pytest-bdd Reuse fixture
  2. For given steps you can also add a target_fixture="<fixture-name>" in the @given: pytest-bdd create fixture in given