Test function with lru_cache decorator - python

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)

Related

Mock class in Python with decorator patch

I would like to patch a class in Python in unit testing. The main code is this (mymath.py):
class MyMath:
def my_add(self, a, b):
return a + b
def add_three_and_two():
my_math = MyMath()
return my_math.my_add(3, 2)
The test class is this:
import unittest
from unittest.mock import patch
import mymath
class TestMyMath(unittest.TestCase):
#patch('mymath.MyMath')
def test_add_three_and_two(self, mymath_mock):
mymath_mock.my_add.return_value = 5
result = mymath.add_three_and_two()
mymath_mock.my_add.assert_called_once_with(3, 2)
self.assertEqual(5, result)
unittest.main()
I am getting the following error:
AssertionError: Expected 'my_add' to be called once. Called 0 times.
The last assert would also fail:
AssertionError: 5 != <MagicMock name='MyMath().my_add()' id='3006283127328'>
I would expect that the above test passes. What I did wrong?
UPDATE:
Restrictions:
I would not change the tested part if possible. (I am curious if it is even possible, and this is the point of the question.)
If not possible, then I want the least amount of change in the to be tested part. Especially I want to keep the my_add() function non-static.
Instead of patching the entire class, just patch the function.
class TestMyMath(unittest.TestCase):
#patch.object(mymath.MyMath, 'my_add')
def test_add_three_and_two(self, m):
m.return_value = 5
result = mymath.add_three_and_two()
m.assert_called_once_with(3, 2)
self.assertEqual(5, result)
I think the original problem is that my_math.my_add produces a new mock object every time it is used; you configured one Mock's return_value attribute, but then checked if another Mock instance was called. At the very least, using patch.object ensures you are disturbing your original code as little as possible.
Your code is almost there, some small changes and you'll be okay:
my_add should be a class method since self does not really play a role here.
If my_add is an instance method, then it will be harder to trace the calls, since your test will track the instance signature, not the class sig
Since you are are patching, not stubbing, you should use the "real thing", except when mocking the return value.
Here's what that looks like in your code:
class MyMath:
#classmethod
def my_add(cls, a, b):
return a + b
def add_three_and_two():
return MyMath.my_add(3, 2)
Now, the test:
import unittest
from unittest.mock import patch, MagicMock
import mymath
class TestMyMath(unittest.TestCase):
#patch('mymath.MyMath')
def test_add_three_and_two(self, mymath_mock):
# Mock what `mymath` would return
mymath_mock.my_add.return_value = 5
# We are patching, not stubbing, so use the real thing
result = mymath.add_three_and_two()
mymath.MyMath.my_add.assert_called_once_with(3, 2)
self.assertEqual(5, result)
unittest.main()
This should now work.

Understanding different ways of mocking or patching a function

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?

Is there a way to mock a complete bit of code in pytest using mock?

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.

Proper way to return mocked object using pytest.fixture

I'm trying to setup the target under test in #pytest.fixture and use it in all my tests in the module. I'm able to patch the test correctly, but after I add the #pytest.fixture to return the mock object and invoke the mocked object in other unit tests the object starting to refer back to the original function.
Following is the code I have. I was expecting the mocked_worker in the unit test to refer to the return value, but it is invoking the actual os.getcwd method instead.
Please help me correct the code:
import os
import pytest
from unittest.mock import patch
class Worker:
def work_on(self):
path = os.getcwd()
print(f'Working on {path}')
return path
#pytest.fixture()
def mocked_worker():
with patch('test.test_module.os.getcwd', return_value="Testing"):
result = Worker()
return result
def test_work_on(mocked_worker):
ans = mocked_worker.work_on()
assert ans == "Testing"
The problem is that when the worker returns the scope of "with" statement ends making the object take its real value, the solution is to use "yield".
#pytest.fixture()
def mocked_worker():
with patch('test.test_module.os.getcwd', return_value="Testing"):
result = Worker()
yield result
I would recommend to use pytest-mock. So full example of one file (test_file.py) solution using this library would be:
import os
import pytest
from unittest.mock import patch
class Worker:
def work_on(self):
path = os.getcwd()
print(f'Working on {path}')
return path
#pytest.fixture()
def mocked_worker(mocker): # mocker is pytest-mock fixture
mocker.patch('test_file.os.getcwd', return_value="Testing")
def test_work_on(mocked_worker):
worker = Worker() # here we create instance of Worker, not mock itself!!
ans = worker.work_on()
assert ans == "Testing"
used libraries for reference:
pytest==5.3.0
pytest-mock==1.12.1

What is the canonical way to check if a function has been called in Python unittest without use of a mock?

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)

Categories

Resources