The Python 3 (>= 3.3) standard library has a great tutorial about Mock in the official documentation. For Python 2, you can use the backported library: Mock on PyPi.
Here is a sample usage. Say you want to mock the call to os.system in this function:
import os
def my_function(src_dir):
os.system('ls ' + src_dir)
To do that, you can use the unittest.mock.patch decorator, like this:
import unittest.mock
@unittest.mock.patch('os.system')
def test_my_function(os_system):
# type: (unittest.mock.Mock) -> None
my_function("/path/to/dir")
os_system.assert_called_once_with('ls /path/to/dir')
This test function will patch the os.system call during its execution. os.system is restored at the end.
Then, there are several "assert" method to check the calls, the parameters, and the results. You can also check that an exception is raised in certain circonstances.
Answer from Laurent LAPORTE on Stack OverflowThe Python 3 (>= 3.3) standard library has a great tutorial about Mock in the official documentation. For Python 2, you can use the backported library: Mock on PyPi.
Here is a sample usage. Say you want to mock the call to os.system in this function:
import os
def my_function(src_dir):
os.system('ls ' + src_dir)
To do that, you can use the unittest.mock.patch decorator, like this:
import unittest.mock
@unittest.mock.patch('os.system')
def test_my_function(os_system):
# type: (unittest.mock.Mock) -> None
my_function("/path/to/dir")
os_system.assert_called_once_with('ls /path/to/dir')
This test function will patch the os.system call during its execution. os.system is restored at the end.
Then, there are several "assert" method to check the calls, the parameters, and the results. You can also check that an exception is raised in certain circonstances.
Just want to add an important detail.
If your code uses import system for example like this:
myls.py:
import os
def do_ls():
os.system('ls')
Then the patch in your test should look like this:
test_myls.py:
from unittest.mock import patch
@patch('os.system')
def test_do_ls(mock_system):
do_ls()
mock_system.assert_called()
However, if the code uses from os import system, e.g. like this:
myls.py:
from os import system
def do_ls():
system('ls')
Then the patch in your test should look like this:
test_myls.py:
from unittest.mock import patch
@patch('myls.system')
def test_do_ls(mock_system):
do_ls()
mock_system.assert_called()
This eluded me for a bit because I had forgotten to read the section on where to patch as I originally intended. If patching does not seem to work, this is one of the points to look at.
If you target multiple OSs, you most probably have to test your program on each of them. These tests should typically include running your test suite on the desired OS. So the straightforward solution for unit testing the above method is to adapt the expected result on platform.system() exactly the same way you showed above:
def testUpdateString():
var expectedResult=""
if platform.system() == 'Darwin':
expectedResult="http://download.com/to/OSX/version"
elif platform.system() == 'Linux':
expectedResult="http://download.com/to/Linux/version"
elif platform.system() == 'Windows':
expectedResult="http://download.com/to/Windows/version"
else:
expectedResult="Seek advice on how to update."
assertEqual(expectedResult,updateString())
Of course, the drawback is that you cannot get a full test coverage of your code when you run the tests on just one OS. In this example, this is probably acceptable, but if the "real" methods you want to test are more complicated, it may become more practicle to unit test the different parts without actually changing the OS. If that's the case, refactor the core part into a separate method which gets platform.system() as a parameter:
def updateStringPerSystem(opSys):
if opSys == 'Darwin':
return "http://download.com/to/OSX/version"
elif opSys == 'Linux':
return "http://download.com/to/Linux/version"
elif opSys == 'Windows':
return "http://download.com/to/Windows/version"
else:
return "Seek advice on how to update."
def updateString():
return updateStringPerSystem(platform.system())
Now you can write unit tests for updateStringPerSystem, passing all os strings you like for different tests, allowing to test the different branches for OS1, OS2, OS3, etc. even when you are running your test suite only under OS1.
Note that this works only for basic unit tests. As soon as you want to test a function which actually uses the return value for updateString in a OS dependent way, you cannot expect to test Windows functionality when running your suite under Linux, or vice versa.
Using the mock framework, you can patch platform.system().
from unittest.mock import patch
…
def testUpdateStringWithDarwin():
with patch('platform.system', MagicMock(return_value="Darwin")):
result = updateString()
assert result == "http://download.com/to/OSX/version", "should be Darwin!"
To update the environment, and restore it after the test:
import os
from unittest import mock
@pytest.fixture()
def setenvvar(monkeypatch):
with mock.patch.dict(os.environ, clear=True):
envvars = {
"API_AUDIENCE": "https://mock.com",
}
for k, v in envvars.items():
monkeypatch.setenv(k, v)
yield # This is the magical bit which restore the environment after
This is just a fixture, you can use it (or not) in any test you want, at the scope you want, no extra dependencies.
You can use setenv:
def test_something(self, monkeypatch):
monkeypatch.setenv("API_AUDIENCE", "https://mock.com")
importlib.reload(conf)
assert "mock" in Config.auth
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
# 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()
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>)