Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python mock Patch os.environ and return value

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?

like image 581
immrsteel Avatar asked Jul 23 '15 09:07

immrsteel


People also ask

How do you mock a value in Python?

How do we mock in Python? Mocking in Python is done by using patch to hijack an API function or object creation call. When patch intercepts a call, it returns a MagicMock object by default. By setting properties on the MagicMock object, you can mock the API call to return any value you want or raise an Exception .

What is Autospec in mock patch?

When you patch a function using mock, you have the option to specify autospec as True: If you set autospec=True then the mock with be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced.

What does OS environ get do?

environ in Python is a mapping object that represents the user's environmental variables. It returns a dictionary having user's environmental variable as key and their values as value.


3 Answers

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://stackoverflow.com/a/67477901/248616
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()
like image 84
vks Avatar answered Oct 20 '22 15:10

vks


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>)
like image 49
Toote Avatar answered Oct 20 '22 13:10

Toote


The accepted answer is correct. Here's a decorator @mockenv to do the same.

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"

like image 12
Adam Hughes Avatar answered Oct 20 '22 14:10

Adam Hughes