简体   繁体   中英

use mock.patch.dict() to patch os.environ in an imported class

I want to write some tests on a class that calls os.environ in its __init__() function and want to use mock.patch.dict() for calls to os.environ . I'm pretty sure I've done this successfully in the past but I can't get it working in this case.

I've put together a repro of the problem. Here's the class under test:

import os


class Widget():
    def __init__(
            self,
            foo: str = os.environ['SOME_VAR']
    ):
        self.bar = foo

    def func1(self):
        return self.bar

and my test class (in dir tests ):

import unittest
import mock
from widget import Widget


@mock.patch.dict(
    "widget.os.environ",
    {"SOME_VAR": "qwerty"}
)
class TestWidget(unittest.TestCase):
    def test1(self):
        widget1 = Widget()
        assert widget1.func1() == "qwerty"

I'm using pytest as my test runner. When I run the tests I get error:

tests/test_widget.py:3: in
from widget import Widget
widget.py:4: in
class Widget():
widget.py:7: in Widget
foo: str = os.environ['SOME_VAR']
../../../../.virtualenvs/pytest-issue-demo-5A-IlS7_/lib/python3.7/os.py:678: in getitem
raise KeyError(key) from None
E KeyError: 'SOME_VAR'

so it seems the call to os.environ wasn't patched. Try as I might I haven't been able to patch it.

My repro is available at https://github.com/jamiekt/pytest-issue-demo . Would appreciate someone taking a look and telling me how I can patch the call to os.environ so that I can successfully run my tests.

It uses pipenv to create a virtualenv. Assuming pipenv & make are installed running

git clone git@github.com:jamiekt/pytest-issue-demo.git
cd pytest-issue-demo
make init test

should run the tests.

The call to os.environ is part of the argument list to this function, not its body. As such, the expression os.environ["SOME_VAR"] is evaluated immediately when the module is loaded -- it's impossible to patch after the fact, because the error occurs immediately on import!

This is a poor design pattern, and should be avoided -- default values for function arguments should be limited to simple expressions which don't have side effects. But if you can't change this code, probably the easiest way to work around this will be to modify the "real" os.environ while loading the module:

try:
    os.environ["SOME_VAR"] = "qwerty"
    from widget import Widget
finally:
    del os.environ["SOME_VAR"]

(If you expect that the environment variable might already be set, you'll need to be a little more careful about saving and restoring its state.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM