pytest-dev / pytest

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
https://pytest.org
MIT License
11.98k stars 2.66k forks source link

monkeypatch.setenv does not work consistently using cli #8685

Closed lachhebo closed 3 years ago

lachhebo commented 3 years ago

monkeypatch does not work reliably as it should. Actually, my test is running well well launched from pytest gui but not when run with cli.

Package Version


api-wafermark 0.1.0 appdirs 1.4.4 attrs 21.2.0 bcrypt 3.2.0 certifi 2020.12.5 cffi 1.14.5 cfgv 3.2.0 chardet 4.0.0 click 7.1.2 coverage 5.5 cycler 0.10.0 distlib 0.3.1 ecdsa 0.14.1 fastapi 0.63.0 filelock 3.0.12 flake8 3.9.2 freezegun 1.1.0 glob2 0.7 h11 0.12.0 identify 2.2.4 idna 2.10 iniconfig 1.1.1 kiwisolver 1.3.1 Mako 1.1.4 MarkupSafe 2.0.0 matplotlib 3.4.2 mccabe 0.6.1 mypy 0.812 mypy-extensions 0.4.3 nodeenv 1.6.0 numpy 1.20.3 packaging 20.9 parse 1.19.0 parse-type 0.5.2 passlib 1.7.4 Pillow 8.2.0 pip 21.0.1 pluggy 0.13.1 pre-commit 2.12.1 py 1.10.0 pyasn1 0.4.8 pycodestyle 2.7.0 pycparser 2.20 pydantic 1.8.2 pyflakes 2.3.1 pyparsing 2.4.7 pytest 6.2.4 pytest-bdd 4.0.2 pytest-cov 2.11.1 python-dateutil 2.8.1 python-jose 3.2.0 python-multipart 0.0.5 PyYAML 5.4.1 requests 2.25.1 rsa 4.7.2 setuptools 56.0.0 six 1.16.0 starlette 0.13.6 toml 0.10.2 typed-ast 1.4.3 typing-extensions 3.10.0.0 urllib3 1.26.4 uvicorn 0.13.4 virtualenv 20.4.6 wheel 0.36.2

macos catalina with pytest 6.2.4

This test:

from datetime import timedelta, datetime
from unittest.mock import patch

from freezegun import freeze_time

TESTED_MODULE = 'api_wafermark.security.access_token'

@freeze_time("2012-01-14")
@patch(f'{TESTED_MODULE}.jwt.encode')
def test_create_access_token__expires_in_defined_time(mock_jwt_encode, monkeypatch):
    # given

    monkeypatch.setenv('JWT_SECRET_KEY', 'SECRET_K')
    monkeypatch.setenv('ALGORITHM', 'ALGO')
    monkeypatch.setenv('DB_URL', 'postgresql://user:pass@localhost:5432/wafermark')

    from api_wafermark.security.access_token import create_access_token

    data_ = {'secret': 'isma is the best'}
    treated_data = {'secret': 'isma',
                    'exp': datetime.utcnow() + timedelta(minutes=15)
                    }

    # when
    create_access_token(data_, 15)

    # then
    mock_jwt_encode.assert_called_with(treated_data, 'SECRET_K', algorithm='ALGO')

For this file:

import os
from datetime import datetime, timedelta
from typing import Optional, Dict

from jose import jwt

SECRET_KEY = os.getenv('JWT_SECRET_KEY')
ALGORITHM = os.getenv('ALGORITHM', 'HS256')
ACCESS_TOKEN_EXPIRE_MINUTES = os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES', 60)

def create_access_token(data: Dict, expires_delta: Optional[int] = ACCESS_TOKEN_EXPIRE_MINUTES):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=expires_delta)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

i have this error:

>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: encode({'secret': 'isma', 'exp': FakeDatetime(2012, 1, 14, 0, 15)}, 'SECRET_K', algorithm='ALGO')
E           Actual: encode({'secret': 'isma', 'exp': FakeDatetime(2012, 1, 14, 0, 15)}, None, algorithm='HS256')
RonnyPfannschmidt commented 3 years ago

this is a user error

monkeypatching the environment after you pulled the values will not change the pulled values

the suggestion would be to either monkeypatch the module directly and/or to do on demand loading of the env vars

lachhebo commented 3 years ago

I know, that is why i import the module after using monkeypatch, most of the time it worked but not this time which is kind of strange.

(it is working when i run the test using pycharm but not when i use the cli)

The-Compiler commented 3 years ago

I'm converting this to a discussion as this definitely isn't an issue with pytest, like @RonnyPfannschmidt said.

Imports in Python are cached, so likely something else in your tests is already importing the module earlier. Consider e.g. adding 1/0 to the module, to provoke a ZeroDevisionError and find out where/how that happens.