Testing Utilities

Envolved makes testing environment variables easy with the monkeypatch attribute and patch() context method. They allows you to set a predefined EnvVar value and then restore the original value when the test is finished.

cache_time_ev = env_var('CACHE_TIME', type=10)

class TestAppStartup(unittest.TestCase):
    def test_startup(self):
        with cache_time_ev.patch(10):
            # now within this context, cache_time_ev.get() will return 10
            my_app.startup()
        self.assertEqual(my_app.cache_time, 10)

note that cache_time_ev.patch(10) just sets attribute cache_time_ev.monkeypatch to 10, and restores it to its previous value when the context is exited. We might as well have done:

cache_time_ev = env_var('CACHE_TIME', type=10)

class TestAppStartup(unittest.TestCase):
    def test_startup(self):
        previous_cache_patch = cache_time_ev.monkeypatch
        cache_time_ev.monkeypatch = 10
        # now within this context, cache_time_ev.get() will return 10
        my_app.startup()
        cache_time_ev.monkeypatch = previous_cache_patch
        self.assertEqual(my_app.cache_time, 10)

Unittest

In unittest tests, we can use the unittest.mock.patch.object method decorate a test method to the values we want to test with.

cache_time_ev = env_var('CACHE_TIME', type=10)

class TestAppStartup(unittest.TestCase):
    @unittest.patch.object(cache_time_ev, 'monkeypatch', 10)
    def test_startup(self):
        # now within this method, cache_time_ev.get() will return 10
        my_app.startup()
        self.assertEqual(my_app.cache_time, 10)

Pytest

When using pytest we can use the monkeypatch fixture fixture to patch our EnvVars.

def test_app_startup(monkeypatch):
    monkeypatch.setattr(cache_time_ev, 'monkeypatch', 10)
    # from now on within this method, cache_time_ev.get() will return 10
    my_app.startup()
    assert my_app.cache_time == 10

Using monkeypatch for different scopes

Sometimes we may want to apply a monkeypatch over a non-function-scope fixture. We will find an error in this case because the built-in monkeypatch fixture is only available in function scope. To overcome this, we can create our own monkeypatch fixture.

from pytest import fixture, MonkeyPatch

@fixture(scope='session')
def session_monkeypatch(request):
    with MonkeyPatch.context() as m:
        yield m

@fixture(scope='session')
def app(session_monkeypatch):
    monkeypatch.setattr(cache_time_ev, 'monkeypatch', 10)
    app = MyApp()
    return app

def test_app_cache_time(app):
    assert app.cache_time == 10

monkeypatch doesn’t affect the environment

An important thing to note is that the monkeypatch fixture doesn’t affect the actual environment, only the specific EnvVar that was patched.

cache_time_ev = env_var('CACHE_TIME', type=int)

def test_one(monkeypatch):
    monkeypatch.setattr(cache_time_ev, 'monkeypatch', 10)
    assert os.getenv('CACHE_TIME') == '10'  # this will fail

cache_time_2_ev = env_var('CACHE_TIME', type=int)

def test_two(monkeypatch):
    monkeypatch.setattr(cache_time_ev, 'monkeypatch', 10)
    assert cache_time_2_ev.get() == 10  # this will fail too

In cases where an environment variable is retrieved from different EnvVars, or with libraries other than envolved, we’ll have to set the environment directly, by using the envvar.SingleEnvVar.key property to get the actual environment name. In pytest we can use the monkeypatch fixture to do this.

cache_time_ev = env_var('CACHE_TIME', type=int)

def test_one(monkeypatch):
    monkeypatch.setenv(cache_time_ev.key, '10')
    assert os.getenv('CACHE_TIME') == '10'

cache_time_2_ev = env_var('CACHE_TIME', type=int)

def test_two(monkeypatch):
    monkeypatch.setenv(cache_time_ev.key, '10')
    assert cache_time_2_ev.get() == 10