Testing in python arround memoized functions - python

I'm building a bunch of tests and discovered that some of them are failing when they are all run together. Looking at why they are breaking I discovered it is because we memoize some of the system calls for the setup.
Because I'm mocking the system calls and not the memoized the function is only computed once and so it leaks that information into the subsequent tests.
What is the elegant way to deal with memoized functions:
Mock those instead of the system calls
Create a parameter on the memoize to not work for tests
Mock the memoize decorator to return the parameters
Find where the data is memoized and clean it at the end of each test
Any other ideas ?
Thanks

Maybe it's too late but I found very easy to use delete_memoized:
# your function
#memoize(timeout=50)
def your_long_function():
# do stuff
# your test
def test_get_folder_content_error_handled(self, flf):
delete_memoized(your_long_function)
your_long_function()
I just used this solution in Django (using django-memoize) and it works like a charm!

Try using MagicMock instead of vanilla Mock.
Say your code is something like:
#memoize.Memoize()
def OAuthClient():
"""Returns an Oauth client."""
return 'blah'
Mocking with Mock like this:
mymodule.OauthPasswordClient = mock.Mock()
Returns an error like this:
TypeError: unsupported operand type(s) for +=: 'int' and 'Mock'
But mocking with MagicMock like this:
mymodule.OauthPasswordClient = mock.MagicMock()
Works just fine.

Related

Python mock not applied to object under test

I am attempting to mock an object to perform some testing
test.py
#patch("api.configuration.client.get_configuration")
def test(mock_client_get_configuration):
mock_client_get_configuration.return_value = "123"
result = code_under_test()
assert result
Inside code_under_test() I make the concrete call to get_configuration().
code.py
from api.configuration.client import get_configuration
def code_under_test():
config = get_configuration("a", "b")
However, whenever I run my test, get_configuration is always the concrete version and not my mock.
If I add a print in my test, I can see that mock_client_get_configuration is a MagicMock.
If I add a print inside code_under_test, then get_configuration is always the actual <function.... and not my mock.
I feel somewhere I am going wrong in how I create my mock. Perhaps it is because my mock does not mimic the two parameters needed on the concrete call so the signature is incorrect?
I'm using Python 3.9 and pytest 7.0.1.

Mocking decorator with that uses hardcoded global variable

When trying to unittest the below seen code snippet i get limited by the timing limit that the decorator that wraps calc_something functions puts to me. It seems that I cant override RAND_RATE on my unittests since then I import the module containing my implementation the decorators have already wrapped my function. How can I solve that issue?
RAND_RATE=20
RAND_PERIOD=10
#limits(calls=RAND_RATE, period=RAND_PERIOD)
def calc_something():
...
Without knowing exactly what limits does, we don't know what (if anything) can be patched. Instead, leave the base implementation undecorated for use by unit test. calc_something will be saved as the separate result of applying limits manually.
RAND_RATE=20
RAND_PERIOD=10
def _do_calc():
...
calc_something = limits(calls=RAND_RATE, period=RAND_PERIOD)(_do_calc)
#limits(calls=RAND_RATE, period=RAND_PERIOD)
def calc_something():
...
Now in your tests, you can define any decorated version you like:
test_me = limits(10, 5)(my_module._do_calc)

How to create a Mock which has functions' special (dunder) attributes

I have some code that uses functions as parameters and I've added some logging that includes __qualname__, this caused my unit tests to fail since the Mock object I passed in raises an AttributeError for __qualname__.
mock_func = Mock()
A simple solution to this problem is to manually add the expected attribute to the mock:
mock_func.__qualname__ = "mock_function"
Or add it to the spec of the mock when I create it:
mock_func = Moc(["__qualname__"])
But these solutions are unsatisfying since I would need to change them whenever I use a different built-in attribute (e.g. __name__).
Is there a simple way to create a Mock which behaves like a function?
The closest I found was this bug report that was opened on the wrong repository, and this request which has no replies.
You can simply use any function as spec for the mock.
mock_func = Mock(spec=max)
mock_func.__qualname__
>>> <Mock name='mock.__qualname__' id='140172665218496'>

Python testing: mocking a function that's imported AND used inside another funciton

Due to circular-import issues which are common with Celery tasks in Django, I'm often importing Celery tasks inside of my methods, like so:
# some code omitted for brevity
# accounts/models.py
def refresh_library(self, queue_type="regular"):
from core.tasks import refresh_user_library
refresh_user_library.apply_async(
kwargs={"user_id": self.user.id}, queue=queue_type
)
return 0
In my pytest test for refresh_library, I'd only like to test that refresh_user_library (the Celery task) is called with the correct args and kwargs. But this isn't working:
# tests/test_accounts_models.py
#mock.patch("accounts.models.UserProfile.refresh_library.refresh_user_library")
def test_refresh_library():
Error is about refresh_library not having an attribute refresh_user_library.
I suspect this is due to the fact that the task(refresh_user_library) is imported inside the function itself, but I'm not too experienced with mocking so this might be completely wrong.
Even though apply_async is your own-created function in your core.tasks, if you do not want to test it but only make sure you are giving correct arguments, you need to mock it. In your question you're mocking wrong package. You should do:
# tests/test_accounts_models.py
#mock.patch("core.tasks.rehresh_user_library.apply_sync")
def test_refresh_library():
In your task function, refresh_user_library is a local name, not an attribute of the task. What you want is the real qualified name of the function you want to mock:
#mock.patch("core.tasks.refresh_user_library")
def test_refresh_library():
# you test here

how to mock function call used by imported pypi library in python

I have the following code that I'm trying to test:
great_report.py
from retry import retry
#retry((ReportNotReadyException), tries=3, delay=10, backoff=3)
def get_link(self):
report_link = _get_report_link_from_3rd_party(params)
if report_link:
return report_link
else:
stats.count("report_not_ready", 1)
raise ReportNotReadyException
I've got my testing function which mocks _get_report_link_from_3rd_party which tests everything but I don't want this function to actually pause execution during when I run tests..
#mock.patch('repo.great_report._get_report_link_from_3rd_party', return_value=None)
test_get_link_raises_exception(self, mock_get_report_link):
self.assertRaises(ReportNotReadyException, get_link)
I tried mocking the retry parameters but am running into issues where get_link keeps retrying over and over which causes long build times instead of just raising the exception and continuing. How can I mock the parameters for the #retry call in my test?
As hinted here, an easy way to prevent the actual sleeping is by patching the time.sleep function. Here is the code that did that for me:
#patch('time.sleep', side_effect = lambda _: None)
There is no way to change decorators parameters after load the module. Decorators decorate the original function and change it at the module load time.
First I would like encourage you to change your design a little to make it more testable.
If you extract the body of get_link() method test the new method and trust retry decorator you will obtain your goal.
If you don't want add a new method to your class you can use a config module that store variables that you use when call retry decorator. After that you can use two different module for testing and production.
The last way is the hacking way where you replace retry.api.__retry_internal by a your version that invoke the original one by changing just the variables:
import unittest
from unittest.mock import *
from pd import get_link, ReportNotReadyException
import retry
orig_retry_internal = retry.api.__retry_internal
def _force_retry_params(new_tries=-1, new_delay=0, new_max_delay=None, new_backoff=1, new_jitter=0):
def my_retry_internals(f, exceptions, tries, delay, max_delay, backoff, jitter, logger):
# call original __retry_internal by new parameters
return orig_retry_internal(f, exceptions, tries=new_tries, delay=new_delay, max_delay=new_max_delay,
backoff=new_backoff, jitter=new_jitter, logger=logger)
return my_retry_internals
class MyTestCase(unittest.TestCase):
#patch("retry.api.__retry_internal", side_effect=_force_retry_params(new_tries=1))
def test_something(self, m_retry):
self.assertRaises(ReportNotReadyException, get_link, None)
IMHO you should use that hacking solution only if you are with the back on the wall and you have no chance to redesign you code to make it more testable. The internal function/class/method can change without notice and your test can be difficult to maintain in the future.

Categories