pytest provides a fixture for this use-case: monkeypatch.syspath_prepend.
You may prepend a path to sys.path list of import locations. Write a fake fwlib.py and include it in your tests, appending the directory as necessary. Like the other test modules, it needn't be included with the distribution.
After playing with this myself, I couldn't actually figure out how to get the fixture to mock module level imports correctly from the library code. By the time the tests run, the library code was already imported and then it is too late to patch.
However, I can offer a different solution that works: you may inject the name from within conftest.py, which gets imported first. The subsequent import statement within the code under test will just re-use the object already present in sys.modules.
Package structure:
$ tree .
.
├── conftest.py
├── lib
│ └── my_lib.py
└── tests
└── test_my_lib.py
2 directories, 3 files
Contents of files:
# conftest.py
import sys
def fwlib_sum(a, b):
return a + b
module = type(sys)('fwlib')
module.sum = fwlib_sum
sys.modules['fwlib'] = module
library file:
# lib/my_lib.py
import fwlib
def fw_sum(a, b):
return fwlib.sum(a, b)
test file:
# lib/test_my_lib.py
import my_lib
def test_sum():
assert my_lib.fw_sum(1, 2) == 3
Answer from wim on Stack Overflowpytest provides a fixture for this use-case: monkeypatch.syspath_prepend.
You may prepend a path to sys.path list of import locations. Write a fake fwlib.py and include it in your tests, appending the directory as necessary. Like the other test modules, it needn't be included with the distribution.
After playing with this myself, I couldn't actually figure out how to get the fixture to mock module level imports correctly from the library code. By the time the tests run, the library code was already imported and then it is too late to patch.
However, I can offer a different solution that works: you may inject the name from within conftest.py, which gets imported first. The subsequent import statement within the code under test will just re-use the object already present in sys.modules.
Package structure:
$ tree .
.
├── conftest.py
├── lib
│ └── my_lib.py
└── tests
└── test_my_lib.py
2 directories, 3 files
Contents of files:
# conftest.py
import sys
def fwlib_sum(a, b):
return a + b
module = type(sys)('fwlib')
module.sum = fwlib_sum
sys.modules['fwlib'] = module
library file:
# lib/my_lib.py
import fwlib
def fw_sum(a, b):
return fwlib.sum(a, b)
test file:
# lib/test_my_lib.py
import my_lib
def test_sum():
assert my_lib.fw_sum(1, 2) == 3
Just to provide a little more details to @wim's good answer, you can use it with submodules too, like so:
import sys
module = type(sys)("my_module_name")
module.submodule = type(sys)("my_submodule_name")
module.submodule.something = sommething
sys.modules["my_module_name"] = module
sys.modules["my_module_name.my_submodule_name"] = module.submodule
How to mock individual functions from an imported module with individual return values?
python - How to mock a imported object with pytest-mock or magicmock - Stack Overflow
python - Mocking an imported function with pytest - Stack Overflow
python - Pytest with mock/pytest-mock - Stack Overflow
I have a file module_to_import.py...
def function_1(foo):
return 'bar' # actual return value isn't just a constant
def function_2(bar):
return 'baz' # actual return value isn't just a constant
...and another file module_to_test.py which imports the previous module and calls its functions inside a class...
import module_to_import
class MyClass:
def method(self):
foo = ...
bar = module_to_import.function_1(foo)
baz = module_to_import.function_2(bar)
return baz
My goal is to write a test for MyClass.method.
All that method does is call other functions/methods. Those functions/methods already have their own tests. So to test only MyClass.method with no confounding side effects, I'd like to simply mock all the objects that get called inside its body, and test only that those objects are called as expected.
But to do this properly with maximum control, I'll need to mock the return values for each object. Given that the entire module_to_import.py is imported, rather than its functions individually, I'm not quite sure how to do this...
If the functions were imported individually, ...
# module_to_test.py
from module_to_import import function_1, function_2
class MyClass:
def method(self):
foo = ...
bar = function_1(foo)
baz = function_2(bar)
return baz
...then it would be as simple as patching them directly from within the namespace of module_to_test...
from unittest.mock import Mock, patch
class TestMyClass:
def test_method(self):
mock_function_1_retval = 'bar'
mock_function_2_retval = 'baz'
with (
patch('module_to_test.function_1', return_value=mock_function_1_retval) as mock_function_1,
patch('module_to_test.function_2', return_value=mock_function_2_retval) as mock_function_2
):
baz = my_class.method()
mock_function_1.assert_called_once_with('foo')
mock_function_2.assert_called_once_with('bar')
But as things are, to access function_1 and function_2, I have to do so through their shared namespace module_to_test.module_to_import, e.g.
from unittest.mock import Mock, patch
class TestMyClass:
def test_method(self):
with patch('module_to_test.module_to_import') as mock_module_to_import:
bar = my_class.method()
# somehow assert `mock_module_to_import.function_1` called with `'foo'`
# somehow assert `mock_module_to_import.function_2` called with `'bar'`In that case, I'm unsure how to give the functions different return values.
Can anyone weigh in here? Maybe it's obvious and I'm just approaching this from the wrong angle...
I believe you are right not testing cases on a real database because it's not unit testing anymore if you are using external dependencies.
There is a possibility to specify return-value and customize it (different return values on each iteration even) for Mock or MagicMock objects.
from unittest.mock import Mock, patch
from app import db_conn
@patch('app.db_conn.find')
def test_some_func1(db_con_mock):
...
assert ...
Keep in mind that in each patch you should specify the import path of db_conn - the path where db_conn **is used (I assume it's a different path in each test), not where it is defined.
To answer the intial question "How to mock a imported object with pytest-mock or magicmock" you can do:
from unittest import mock # because unittest's mock works great with pytest
def test_some_func1():
with mock.patch('some_module1.db', mock.MagicMock(return_value=...)) as magicmock:
result = some_func1(...)
assert ... e.g. different fields of magicmock
assert expected == result
# or alternatively use annotations
@mock.patch('some_module2.db', mock.MagicMock(return_value=...))
def test_some_func2():
result = some_func2(...)
note that you do not patch the actual source of db
For your other use case
I want to mock the database (using a mongo database), more specifically the "db_conn" object
you similarly follow the hints of the link above:
mock.patch('some_module1.db_conn', mock.MagicMock(return_value=...))
Given that, you will notice in your tests that db from `db = db_conn.db_name2.db_collection2' will create another mock object. Calls to that object will be recorded as well. In such a way you will be able to trace history of calls and values assignments as well.
Furthermore, see an example how to pach mongo db.
For testing of Flask apps see the documentation of flask. Also this is a nice explanation as well, and uses DB connections.
As a general hint, like @MikeMajara mentioned - separate your code more into smaller functions that are also easy to test. In tradition to TDD: write tests first, implement later, and refactor (especially DRY!)
Since you are using pytest, I would suggest using pytest's built-in 'monkeypatch' fixture.
Consider this simple setup:
We define the function to be mocked.
"""`my_library.py` defining 'foo'."""
def foo(*args, **kwargs):
"""Some function that we're going to mock."""
return args, kwargs
And in a separate file the class that calls the function.
"""`my_module` defining MyClass."""
from my_library import foo
class MyClass:
"""Some class used to demonstrate mocking imported functions."""
def should_call_foo(self, *args, **kwargs):
return foo(*args, **kwargs)
We mock the function where it is used using the 'monkeypatch' fixture
"""`test_my_module.py` testing MyClass from 'my_module.py'"""
from unittest.mock import Mock
import pytest
from my_module import MyClass
def test_mocking_foo(monkeypatch):
"""Mock 'my_module.foo' and test that it was called by the instance of
MyClass.
"""
my_mock = Mock()
monkeypatch.setattr('my_module.foo', my_mock)
MyClass().should_call_foo(1, 2, a=3, b=4)
my_mock.assert_called_once_with(1, 2, a=3, b=4)
We could also factor out the mocking into its own fixture if you want to reuse it.
@pytest.fixture
def mocked_foo(monkeypatch):
"""Fixture that will mock 'my_module.foo' and return the mock."""
my_mock = Mock()
monkeypatch.setattr('my_module.foo', my_mock)
return my_mock
def test_mocking_foo_in_fixture(mocked_foo):
"""Using the 'mocked_foo' fixture to test that 'my_module.foo' was called
by the instance of MyClass."""
MyClass().should_call_foo(1, 2, a=3, b=4)
mocked_foo.assert_called_once_with(1, 2, a=3, b=4)
What you are mocking with the line emails.send_email = MagicMock() is the function
class CarsEmails:
def send_email(self):
...
that you don't have. This line will thus only add a new function to your emails object. However, this function is not called from your code and the assignment will have no effect at all. Instead, you should mock the function send_email from the cars.lib.email module.
mocking the function where it is used
Once you have imported the function send_email via from cars.lib.email import send_email in your module format_email.py, it becomes available under the name format_email.send_email. Since you know the function is called there, you can mock it under its new name:
from unittest.mock import patch
from format_email import CarsEmails
@pytest.mark.parametrize("test_input,expected_output", test_data)
def test_email_payload_formatting(config, test_input, expected_output):
emails = CarsEmails(email_client=MagicMock(), config=config)
with patch('format_email.send_email') as mocked_send:
emails.send_cars_email(*test_input)
mocked_send.assert_called_with(*expected_output)
mocking the function where it is defined
Update:
It really helps to read the section Where to patch in the unittest docs (also see the comment from Martijn Pieters suggesting it):
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
So stick with the mocking of the function in usage places and don't start with refreshing the imports or aligning them in correct order. Even when there should be some obscure usecase when the source code of format_email would be inaccessible for some reason (like when it is a cythonized/compiled C/C++ extension module), you still have only two possible ways of doing the import, so just try out both mocking possibilities as described in Where to patch and use the one that succeeds.
Original answer:
You can also mock send_email function in its original module:
with patch('cars.lib.email.send_email') as mocked_send:
...
but be aware that if you have called the import of send_email in format_email.py before the patching, patching cars.lib.email won't have any effect on code in format_email since the function is already imported, so the mocked_send in the example below won't be called:
from format_email import CarsEmails
...
emails = CarsEmails(email_client=MagicMock(), config=config)
with patch('cars.lib.email.send_email') as mocked_send:
emails.send_cars_email(*test_input)
mocked_send.assert_called_with(*expected_output)
To fix that, you should either import format_email for the first time after the patch of cars.lib.email:
with patch('cars.lib.email.send_email') as mocked_send:
from format_email import CarsEmails
emails = CarsEmails(email_client=MagicMock(), config=config)
emails.send_cars_email(*test_input)
mocked_send.assert_called_with(*expected_output)
or reload the module e.g. with importlib.reload():
import importlib
import format_email
with patch('cars.lib.email.send_email') as mocked_send:
importlib.reload(format_email)
emails = format_email.CarsEmails(email_client=MagicMock(), config=config)
emails.send_cars_email(*test_input)
mocked_send.assert_called_with(*expected_output)
Not that pretty either way, if you ask me. I'd stick with mocking the function in the module where it is called.