I have a function I want to unit test contains calls two other functions. I am unsure how can I mock both functions at the same time properly using patch. I have provided an example of what I mean below. When I run nosetests, the tests pass but I feel that there must be a cleaner way to do this and I do not really Understand the piece regarding f.close()...
The directory structure looks like this:
program/
program/
data.py
tests/
data_test.py
data.py:
import cPickle
def write_out(file_path, data):
f = open(file_path, 'wb')
cPickle.dump(data, f)
f.close()
data_test.py:
from mock import MagicMock, patch
def test_write_out():
path = '~/collection'
mock_open = MagicMock()
mock_pickle = MagicMock()
f_mock = MagicMock()
with patch('__builtin__.open', mock_open):
f = mock_open.return_value
f.method.return_value = path
with patch('cPickle.dump', mock_pickle):
write_out(path, 'data')
mock_open.assert_called_once_with('~/collection', 'wb')
f.close.assert_any_call()
mock_pickle.assert_called_once_with('data', f)
Results:
$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK
You can simplify your test by using the patch decorator and nesting them like so (they are MagicMock objects by default):
from unittest.mock import patch
#patch('cPickle.dump')
#patch('__builtin__.open')
def test_write_out(mock_open, mock_pickle):
path = '~/collection'
f = mock_open.return_value
f.method.return_value = path
write_out(path, 'data')
mock_open.assert_called_once_with('~/collection', 'wb')
mock_pickle.assert_called_once_with('data', f)
f.close.assert_any_call()
Calls to a MagicMock instance return a new MagicMock instance, so you can check that the returned value was called just like any other mocked object. In this case f is a MagicMock named 'open()' (try printing f).
In addition to the response #Matti John you can also use patch inside function test_write_out:
from mock import MagicMock, patch
def test_write_out():
path = '~/collection'
with patch('__builtin__.open') as mock_open, \
patch('cPickle.dump') as mock_pickle:
f = mock_open.return_value
...
As of Python 3.10 you can do use Parenthesized Context Managers like this
from unittest.mock import patch
def test_write_out():
with (
patch('cPickle.dump'),
patch('__builtin__.open') as open_mock, # example of using `as`
):
yield
Here's a simple example on how to test raising ConflictError in create_collection function using mock:
import os
from unittest import TestCase
from mock import patch
from ..program.data import ConflictError, create_collection
class TestCreateCollection(TestCase):
def test_path_exists(self):
with patch.object(os.path, 'exists') as mock_method:
mock_method.return_value = True
self.assertRaises(ConflictError, create_collection, 'test')
Please, also see mock docs and Michael Foord's awesome introduction to mock.
Related
I am using pytest fixtures to give a custom object to my tests. I want a fresh object for each test since some of the tests modify the object.
from gen3release.filesys import io
import pytest
from gen3release.config import env
import json
from ruamel.yaml import YAML
import hashlib
import os
import copy
#pytest.fixture(scope="function")
def env_obj():
return env.Env("./data/test_environment.$$&")
The default scope is function which means that the object should give me a new object each for each test.
I have 5 tests for a module called io.py
When I run this test
def test_store_environment_params(env_obj, loaded_env_obj):
io.store_environment_params("./data/test_environment.$$&", env_obj,"manifest.json")
sowers = env_obj.sower_jobs
expected_sower = loaded_env_obj.sower_jobs
assert expected_sower == sowers
env_params = env_obj.ENVIRONMENT_SPECIFIC_PARAMS
expected_params = loaded_env_obj.ENVIRONMENT_SPECIFIC_PARAMS
assert expected_params["manifest.json"] == env_params["manifest.json"], f"Got: {env_params}"
io.store_environment_params("./data/test_environment.$$&/manifests/hatchery/", env_obj,"hatchery.json")
assert expected_params["hatchery.json"] == env_params["hatchery.json"]
Before this test
def test_merge_json_file_with_stored_environment_params(env_obj, loaded_env_obj):
os.system("cp ./data/test_manifest.json ./data/manifest.json")
# h1, j1 = io.read_manifest("./data/manifest.json")
env_params = env_obj.ENVIRONMENT_SPECIFIC_PARAMS["manifest.json"]
print(env_params)
io.merge_json_file_with_stored_environment_params("./data", "manifest.json", env_params, env_obj, loaded_env_obj)
with open("./data/manifest.json") as f:
with open("./data/testmerge_manifest.json") as f2:
assert f2.read() == f.read()
os.system("rm ./data/manifest.json")
This test fails because the env_obj the test receives has been modified. How do I get pytest to give me a new object every time?
I'm having trouble testing that a method is called using mock -- as a simple example, let's say that the method is os.getcwd. I want to test that my own function, pickle_wdir, is calling os.getcwd as intended. However, the function I am testing pickles the value returned by os.getcwd, which results in an error.
Here's a simple example to reproduce the error.
os_ex.py:
import os
import pickle
def pickle_wdir(filename):
dir = os.getcwd()
with open(filename, 'wb') as handle:
pickle.dump(dir, handle)
test_os_ex.py:
from unittest import TestCase
from unittest.mock import patch
from os_ex import pickle_wdir
class TestPickleWdir(TestCase):
def test_os_called(self):
fname = 'dir.pickle'
with patch('os_ex.os') as mocked_obj:
pickle_wdir(fname)
mocked_obj.getcwd.assert_called()
The error message returned is
_pickle.PicklingError: Can't pickle <class 'unittest.mock.MagicMock'>: it's not the same object as unittest.mock.MagicMock.
How can I test that os.getcwd is called without getting this PicklingError?
You need to mock os.getcwd(), open() and pickle.dump() methods. You can use unittest.mock.patch as context manager to do this.
E.g.
os_ex.py:
import os
import pickle
def pickle_wdir(filename):
dir = os.getcwd()
with open(filename, 'wb') as handle:
pickle.dump(dir, handle)
test_os_ex.py:
import unittest
from unittest.mock import patch, mock_open
from os_ex import pickle_wdir
class TestOsEx(unittest.TestCase):
def test_pickle_wdir(self):
fname = 'dir.pickle'
m = mock_open(read_data='mocked data')
with patch('os_ex.os') as mocked_obj, patch('builtins.open', m) as mocked_open, patch('pickle.dump') as mocked_dump:
mocked_obj.getcwd.return_value = '/root'
pickle_wdir(fname)
mocked_obj.getcwd.assert_called()
m.assert_called_with(fname, 'wb')
handle = mocked_open()
mocked_dump.assert_called_with('/root', handle)
if __name__ == '__main__':
unittest.main()
unit test results with 100% coverage:
.
----------------------------------------------------------------------
Ran 1 test in 0.012s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
src/stackoverflow/60627827/os_ex.py 6 0 100%
src/stackoverflow/60627827/test_os_ex.py 17 0 100%
------------------------------------------------------------------------
TOTAL 23 0 100%
I have a class with a method which uses shutil.rmtree to remove some files if a param is passed as true, How to mock this behavior so as other tests don't break which needs these files.
My class looks like this -
class FileConverter(object):
def __init__(self, path_to_files):
self._path_to_files = path_to_files
def convert_files(self, rmv_src=False):
doStuff()
if rmv_src:
shutil.rmtree(self.__path_to_files)
def doStuff():
# does some stuff
Now my tests look like -
class TestFileConverter(unittest.TestCase):
def test_convert_success(self):
input_dir = 'resources/files'
file_converter = FileConverter(input_dir)
file_converter.convert_files()
# assert the things from doStuff
#mock.patch('shutil.rmtree')
def test_convert_with_rmv(self, rm_mock):
input_dir = 'resources/files'
file_converter = FileConverter(input_dir)
file_converter.convert_files(True)
self.assertEquals(rm_mock, [call(input_dir)])
Now when I run this testsuite the test with rmv gives me assertionError
<MagicMock name='rmtree' id='139676276822984'> != [call('resources/images')]
and the first test gives me file not found error since the mock did not work and the rmv source test removed the file
FileNotFoundError: [Errno 2] No such file or directory: 'resources/images'
If I comment out the second test with rmv_source true then my first test works fine.
What am I doing wrong here?
Your module has already imported shutil.rmtree so mocking it later in the test suite won't do anything.
You need to mock the module when you import FileConverter, not afterwards.
import sys
from mock import MagicMock
sys.modules['shutil'] = MagicMock()
# and/or
sys.modules['shutil.rmtree'] = MagicMock()
import FileConverter
If you still need to use shutil in your test code, then first import it using an alias,and use that when you need the 'real' module:
import sys
from mock import MagicMock
import shutil as shutil_orig
sys.modules['shutil'] = MagicMock()
import shutil
print(type(shutil_orig.rmtree))
# <class 'function'>
print(type(shutil.rmtree))
# <class 'mock.mock.MagicMock'>
The original post should work except the call(input_dir) did not work for me
#mock.patch('shutil.rmtree')
def test_convert_with_rmv(self, rm_mock):
input_dir = 'resources/files'
rm_mock.return_value = 'REMOVED'
file_converter = FileConverter(input_dir)
file_converter.convert_files(True)
rm_mock.assert_called_with(input_dir)
self.assertEqual(rm_mock.return_value, 'REMOVED')
The test_convert_with_rmv has no way removed the input_dir, it probably never created in the first place. You could assert this statement in each test before and after convert_files called:
self.asserTrue(os.path.isdir(input_dir))
My file (ensure_path.py):
import os
def ensure_path(path):
if not os.path.exists(path):
os.makedirs(path)
return path
My test:
import unittest
from unittest.mock import patch, MagicMock
from src.util.fs.ensure_path import ensure_path
FAKE_PATH = '/foo/bar'
class EnsurePathSpec(unittest.TestCase):
#patch('os.path.exists', side_effect=MagicMock(return_value=False))
#patch('os.makedirs', side_effect=MagicMock(return_value=True))
def test_path_exists_false(self, _mock_os_path_exists_false, _mock_os_makedirs):
ensure_path(FAKE_PATH)
_mock_os_path_exists_false.assert_called_with(FAKE_PATH)
_mock_os_makedirs.assert_called_with(FAKE_PATH)
#patch('os.path.exists', side_effect=MagicMock(return_value=True))
#patch('os.makedirs', side_effect=MagicMock(return_value=True))
def test_path_exists_true(self, _mock_os_path_exists_true, _mock_os_makedirs):
ensure_path(FAKE_PATH)
_mock_os_path_exists_true.assert_called_with(FAKE_PATH)
_mock_os_makedirs.assert_not_called()
This is giving the failed assertion Expected call: makedirs('/foo/bar') which I think makes sense because I think I'm mocking os.makedirs at the wrong level.
I've tried replacing #patch('os.makedirs', with #patch('src.util.fs.ensure_path.os.makedirs', and a couple variations of that but I get
ImportError: No module named 'src.util.fs.ensure_path.os'; 'src.util.fs.ensure_path' is not a package
Here is my __init__.py flow :
Is there an obvious fix I'm missing?
Your patch arguments need to be in the reverse order of the #patch decorators.
I have a class method that I am trying to test that requires two patched methods, ConfigB.__init__ and listdir:
from os import listdir
from config.ConfigB import ConfigB
class FileRunner(object):
def runProcess(self, cfgA)
cfgB = ConfigB(cfgA)
print(listdir())
I have the following test set up:
import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner
class TestFileRunner(unittest.TestCase):
#mock.patch('ConfigB.ConfigB.__init__')
#mock.patch('os.listdir')
def test_methodscalled(self, osListDir, cfgB):
cfgA = MagicMock()
fileRunner = FileRunner()
cfgB.return_value = None
osListDir.return_value = None
fileRunner.runProcess(cfgA)
Now the patched mock and return value works for ConfigB.ConfigB, but it does not work for os.listdir. When the print(listdir()) method runs I get a list of file in the current directory, not a value of None as I specified in the patched return value. Any idea what is going wrong?
You need to patch your relative path to your code. patch('os.listdir') doesn't works because you need to patch this:
#mock.patch("path.to.your.pythonfile.listdir")
Try with that.