Python mock Patch os.environ and return value
Asked Answered
M

8

112

Unit testing conn() using mock:

app.py

import mysql.connector
import os, urlparse


def conn():
    if "DATABASE_URL" in os.environ:
        url = urlparse(os.environ["DATABASE_URL"])
        g.db = mysql.connector.connect(
            user=url.username,
            password=url.password,
            host=url.hostname,
            database=url.path[1:],
        )
    else:
        return "Error"

test.py

def test_conn(self):
    with patch(app.mysql.connector) as mock_mysql:
        with patch(app.os.environ) as mock_environ:
            con()
            mock_mysql.connect.assert_callled_with("credentials")

Error: Assertion mock_mysql.connect.assert_called_with is not called.

which I believe it is because 'Database_url' is not in my patched os.environ and because of that test call is not made to mysql_mock.connect.

Questions:

  1. What changes do I need to make this test code work?

  2. Do I also have to patch urlparse?

Matriarchy answered 23/7, 2015 at 9:1 Comment(0)
P
153

You can try unittest.mock.patch.dict solution. Just call conn with a dummy argument:

import mysql.connector
import os, urlparse
from unittest import mock


@mock.patch.dict(os.environ, {"DATABASE_URL": "mytemp"}, clear=True)  # why need clear=True explained here https://mcmap.net/q/193919/-python-mock-patch-os-environ-and-return-value
# If clear is true then the dictionary will be cleared before the new values are set.
def conn(mock_A):
    print os.environ["mytemp"]
    if "DATABASE_URL" in os.environ:
        url = urlparse(os.environ["DATABASE_URL"])
        g.db = mysql.connector.connect(
            user=url.username,
            password=url.password,
            host=url.hostname,
            database=url.path[1:],
        )
    else:
        return "Error"

Or if you don't want to modify your original function try this solution:

import os
from unittest import mock

def func():
    print os.environ["mytemp"]


def test_func():
    k = mock.patch.dict(os.environ, {"mytemp": "mytemp"})
    k.start()
    func()
    k.stop()


test_func()
Panjandrum answered 23/7, 2015 at 9:35 Comment(2)
As it doesn't make a difference for the question / answer, I removed the wrong Python code from both :-)Utopianism
I just want to highlight the patch.dict() option clear=True! I intuitively expected this to be the default behavior, plus the unittest docs examples are technically correct but give the wrong impression, and don't highlight the clear option.Washday
S
53

For this, I find that pytest's monkeypatch fixture leads to better code when you need to set environment variables:

def test_conn(monkeypatch):
    monkeypatch.setenv('DATABASE_URL', '<URL WITH CREDENTIAL PARAMETERS>')
    with patch(app.mysql.connector) as mock_mysql:
        conn()
    mock_mysql.connect.assert_called_with(<CREDENTIAL PARAMETERS>)
Stonewort answered 6/9, 2018 at 20:29 Comment(3)
this gives me AttributeError: 'MockFixture' object has no attribute 'setenv'Dagall
This works for me, using pytest==3.5.0. @SonicSoul maybe your version is different?Natty
@SonicSoul make sure to include monkeypatch in the test argumentsWitham
D
19

The accepted answer is correct. Here's a decorator @mockenv to do the same. Note this doesn't depend on monkeypatch so it works outside of pytest presumably

def mockenv(**envvars):
    return mock.patch.dict(os.environ, envvars)


@mockenv(DATABASE_URL="foo", EMAIL="[email protected]")
def test_something():
    assert os.getenv("DATABASE_URL") == "foo"

Dextran answered 25/6, 2020 at 16:21 Comment(0)
L
15

In my use case, I was trying to mock having NO environmental variable set. To do that, make sure you add clear=True to your patch.

with patch.dict(os.environ, {}, clear=True):
    func()
Lauter answered 10/5, 2021 at 21:28 Comment(1)
This is exactly what I was missing, thanks!Harber
C
14

At the head of your file mock environ before importing your module:

with patch.dict(os.environ, {'key': 'mock-value'}):
    import your.module
Cartelize answered 15/1, 2021 at 15:9 Comment(1)
This helped me out because the key and value I needed were required at import time rather than at the time of the function callPuttier
I
3

You can also use something like the modified_environ context manager describe in this question to set/restore the environment variables.

with modified_environ(DATABASE_URL='mytemp'):
    func()
Iridaceous answered 10/5, 2017 at 20:47 Comment(0)
T
2

Little improvement to answer here

@mock.patch.dict(os.environ, {"DATABASE_URL": "foo", "EMAIL": "[email protected]"})
def test_something():
    assert os.getenv("DATABASE_URL") == "foo"
Tabitha answered 12/8, 2022 at 3:47 Comment(0)
C
0

Issue solved for me, I had to give the same before my import of class.

mock.patch.dict(os.environ, {"CONFIG_SERVER_PATH": "config_path", "CONFIG_SERVER_PROFILE": "config_profile"}).start()
from Class.module import function
Creosote answered 11/3 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.