I am tyring to understand what are the differences between the following three ways of creating a mock function for usage in testing.
Suppose you have the following application.py(from this link):
# application.py
from time import sleep
def is_windows():
# This sleep could be some complex operation instead
sleep(5)
return True
def get_operating_system():
return 'Windows' if is_windows() else 'Linux'
and the following test_application.py
# test_application.py
from unittest.mock import patch
import application
from application import get_operating_system
# * Non-mocked test behaviour
# def test_get_operating_system():
# assert get_operating_system() == 'Windows'
# Mocked test behaviour
# In this example, I mock the slow function and return True always
# - 1) 'mocker' fixture provided by pytest-mock
def test_get_operating_system_1(mocker):
mocker.patch('application.is_windows', return_value=True)
assert get_operating_system() == 'Windows'
# - 2) 'monkeypatch' fixture
def test_get_operating_system_2(monkeypatch):
def mock_is_windows(*args, **kwargs):
return True
monkeypatch.setattr(application, 'is_windows', mock_is_windows)
assert get_operating_system() == 'Windows'
# - 3) 'patch' from unittest.mock
#patch.object(application, 'is_windows', return_value=True)
def test_get_operating_system_3(mock_system):
assert get_operating_system() == 'Windows'
These tests all pass, but seem to behave differently, although from the pytest-mock pytest, and unittest.mock documentations I cannot fully understand those differences.
Therefore these are my "simple" questions:
What are the differences between test 1, 2, 3?
When is it more convenient to use each one of them?
What other strategies can I use to patch tests?
Related
I'm trying to use Pytest to test Python code. The code I'm testing isn't a class but is a bunch of functions in the same file that call other functions in the same file. For example advanced_function1 calls basic_function, and advanced_function2 can also call basic_function.
When I file.basic_function = Mock() in a test it isn't scoped to just that test.
I can verify this because when I run the test (test_advanced_function1) that mocks basic_function then test_advanced_function2 does not pass. However if I only run test_advanced_function2 (meaning basic_function never gets mocked in another test) then it works.
To my knowledge the way to correct this is to use a context manager in test_advanced_function1 and mock basic_function as that context manager. I don't know how to do this.
To simplify things and more directly declare intent I've got a test that checks if the function is a Mock object or not.
myfile.py
def basic_function():
return True
def advanced_function1():
return basic_function()
def advanced_function2():
return not basic_function()
test_myfile.py
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
myfile.basic_function = Mock(return_value='foo')
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
So, how do I use a context manager in test_advanced_function1 to mock basic_function?
EDIT: To clarify, I can't assign the mock as a fixture because there are multiple test cases that run in the real code I'm working on and I can't mock basic_function for all the asserts in advanced_function1. Yes I know I should break this massive test apart into smaller tests but I'm just learning the codebase and don't want to change too much before getting all their tests working again.
The mock.patch context manager is seldom used directly in pytest. Instead we use fixtures. The plugin pytest-mock provides a fixture for using the mock.patch API in a more "pytest-thonic" way, like this:
import pytest
import myfile
def test_basic_function():
assert myfile.basic_function() == True
def test_advanced_function1(mocker):
mocker.patch("myfile.basic_function", return_value="foo")
assert myfile.advanced_function1() == 'foo'
def test_advanced_function2():
assert myfile.advanced_function2() == False
One possible solution is to use patch in a context manager.
test_myfile.py
from unittest.mock import patch
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
with patch('myfile.basic_function') as basic_function:
basic_function.return_value = 'foo'
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
But please let me know if there's a more elegant way to do this!
For instance, every time a test finds
database.db.session.using_bind("reader")
I want to remove the using_bind("reader")) and just work with
database.db.session
using mocker
Tried to use it like this in conftest.py
#pytest.fixture(scope='function')
def session(mocker):
mocker.patch('store.database.db.session.using_bind', return_value=_db.db.session)
But nothing has worked so far.
Code under test:
from store import database
results = database.db.session.using_bind("reader").query(database.Order.id).join(database.Shop).filter(database.Shop.deleted == False).all(),
and I get
AttributeError: 'scoped_session' object has no attribute 'using_bind' as an error.
Let's start with an MRE where the code under test uses a fake database:
from unittest.mock import Mock, patch
class Session:
def using_bind(self, bind):
raise NotImplementedError(f"Can't bind {bind}")
def query(self):
return "success!"
database = Mock()
database.db.session = Session()
def code_under_test():
return database.db.session.using_bind("reader").query()
def test():
assert code_under_test() == "success!"
Running this test fails with:
E NotImplementedError: Can't bind reader
So we want to mock session.using_bind in code_under_test so that it returns session -- that will make our test pass.
We do that using patch, like so:
#patch("test.database.db.session.using_bind")
def test(mock_bind):
mock_bind.return_value = database.db.session
assert code_under_test() == "success!"
Note that my code is in a file called test.py, so my patch call applies to the test module -- you will need to adjust this to point to the module under test in your own code.
Note also that I need to set up my mock before calling the code under test.
So I've this code that mocks two times, the first time by mocking imports with:
sys.modules['random'] = MagicMock()
The second time happens inside the unittest of a function that used that import, for example a function that used random
The tests. py is:
import sys
import unittest
from unittest import mock
from unittest.mock import MagicMock
import foo
sys.modules['random'] = MagicMock()
class test_foo(unittest.TestCase):
def test_method(self):
with mock.patch('random.choice', return_value = 2):
object = foo.FooClass(3)
self.assertEqual(2, object.method(), 'Should be 2')
def test_staticmethod(self):
with mock.patch('random.choice', return_value = 2):
object = foo.FooClass(3)
self.assertEqual(2, object.method(), 'should be 2')
The original file Foo.py is:
import random
class FooClass:
def __init__(self,arg):
self.arg = arg
def method(self):
print(random.choice)
return random.choice([1,2,3])
#staticmethod
def staticmethod():
print(random.choice)
random.choice([1,2,3])
The two mocks contrarrest each other, and the mocking of random doesn't happen.
When it prints random it actually prints:
<<bound method Random.choice of <random.Random object at 0x7fe688028018>>
I want that to print a MagicMock.
Can someone help me understand what's happening? Why are they contrarresting each other?
You don't need to update the module source with sys.modules['random'] = MagicMock() without this line it works fine <MagicMock name='choice' id='...'>. patch already does all the work for the isolated temporary updating the method. See more explanation in the docs - Where to patch
I'm attempting to test a a method that is memoized through lru_cache (since it's an expensive database call). with pytest-mock.
A simplified version of the code is:
class User:
def __init__(self, file):
# load a file
#lru_cache
def get(self, user_id):
# do expensive call
Then I'm testing:
class TestUser:
def test_get_is_called(self, mocker):
data = mocker.ANY
user = User(data)
repository.get(user_id)
open_mock = mocker.patch('builtins.open', mocker.mock_open())
open_mock.assert_called_with('/foo')
But I'm getting the following error:
TypeError: unhashable type: '_ANY'
This happens because functools.lru_cache needs the keys stored to be hashable i.e. have a method __hash__ or __cmp__ implemented.
How can I mock such methods in a mocker to make it work?
I've tried
user.__hash__.return_value = 'foo'
with no luck.
For people arriving here trying to work out how to test functions decorated with lru_cache or alru_cache, the answer is to clear the cache before each test.
This can be done as follows:
def setup_function():
"""
Avoid the `(a)lru_cache` causing tests with identical parameters to interfere
with one another.
"""
my_cached_function.cache_clear()
How to switch off #lru_cache when running pytest
In case you ended up here because you want to test an #lru_cache - decorated function with different mocking (but the lru_cache prevents your mocking) ...
Just set the maxsize of the #lru_cache to 0 if you run pytest!
#lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
Minimal working example
with #lru_cache active (maxsize=256) when code runs and deactivated (maxsize=0) if pytest runs:
import sys
from functools import lru_cache
#lru_cache(maxsize=0 if "pytest" in sys.modules else 256)
def fct_parent():
return fct_child()
def fct_child():
return "unmocked"
def test_mock_lru_cache_internal(monkeypatch):
"""This test fails if #lru_cache of fct_parent is active and succeeds otherwise"""
print(f"{fct_parent.cache_info().maxsize=}")
for ii in range(2):
ret_val = f"mocked {ii}"
with monkeypatch.context() as mpc:
mpc.setattr(f"{__name__}.fct_child", lambda: ret_val) # mocks fct_child to return ret_val
assert fct_parent() == ret_val
if __name__ == "__main__":
"""
This module is designed to fail, if called by python
$ python test_lru_cache_mocking.py
and to work if exectued by pytest
$ pytest -s test_lru_cache_mocking.py
The reason is, that the size of the lru_cache is 256 / 0 respectively
and hence test_mock_lru_cache_internal fails / succeeds.
"""
#
from _pytest.monkeypatch import MonkeyPatch
test_mock_lru_cache_internal(MonkeyPatch())
Instead of using mocker.ANY (an object which is intented to be used in assertions as a placeholder that's equal to any object) I believe you instead want to use a sentinel object (such as mocker.sentinel.DATA).
This appears to work from a quick test:
from functools import lru_cache
#lru_cache(maxsize=None)
def f(x):
return (x, x)
def test(mocker):
ret = f(mocker.sentinel.DATA)
assert ret == (mocker.sentinel.DATA, mocker.sentinel.DATA)
If I have a class similar to the 1 below and I want to test the various cases for the bar function, how can I accomplish this without mocking the private functions? In other words, how in Python's unittest library could I achieve something similar to this:
def test_bar():
f = Foo()
f.bar(3)
expect(self._is_positive_number).toBeCalled()
foo.py
class Foo():
def bar(self, x):
if type(x) is not int:
print('Please enter a valid integer')
return False
if x > 0:
self._is_positive_number()
elif x == 0:
self._is_zero()
else
self._is_negative()
def _is_positive_number(self):
print('Positive')
return True
def _is_zero(self):
print('Zero')
return True
def _is_negative_number(self):
print('Negative')
return True
As far as I know, there's no way to do this without mocking out the private methods. However, the mock library (available as unittest.mock in the standard library as of 3.3, a separate installation otherwise) makes this relatively painless:
try:
# Python 3.3 or later
import unittest.mock as mock
except ImportError:
# Make sure you install it first
import mock
class TestFoo(unittest.TestCase):
def setUp(self):
self.f = Foo()
def test_bar(self):
with mock.patch.object(self.f, '_is_positive_number') as is_pos:
self.f.bar(3)
self.assertTrue(is_pos.called)
Using mock library is a preferred way to go.
Here's a complete example for all three private methods. You can choose shorter names if you prefer, but I'd better stay explicit. Note that, to be safe, you should assert that not only a desired method was called, but that other private methods weren't called:
from unittest import TestCase
from mock import Mock
class MyTestCase(TestCase):
def setUp(self):
self.instance = Foo()
self.instance._is_positive_number = Mock()
self.instance._is_negative_number = Mock()
self.instance._is_zero = Mock()
def test_positive(self):
self.instance.bar(3)
self.assertTrue(self.instance._is_positive_number.called)
self.assertFalse(self.instance._is_negative_number.called)
self.assertFalse(self.instance._is_zero.called)
def test_negative(self):
self.instance.bar(-3)
self.assertFalse(self.instance._is_positive_number.called)
self.assertTrue(self.instance._is_negative_number.called)
self.assertFalse(self.instance._is_zero.called)
def test_zero(self):
self.instance.bar(0)
self.assertFalse(self.instance._is_positive_number.called)
self.assertFalse(self.instance._is_negative_number.called)
self.assertTrue(self.instance._is_zero.called)