#cached_property doctest is not detected - python

I have a code. a.py
from functools import cached_property, cache
import doctest
class C1:
def test_1(self):
"""
>>> C1().test_1()
'f'
"""
return "f"
#property
def test_2(self):
"""
>>> C1().test_2
'p'
"""
return "p"
#cached_property
def test_3(self):
"""
>>> C1().test_3
'cp'
"""
return "cp"
#cache
def test_4(self):
"""
>>> C1().test_4()
'c'
"""
return "c"
doctest.testmod()
test_3 is a function decorated by #cached_property.
It has a doctest. but that was not executed.
$ python3 a.py -v
Trying:
C1().test_1()
Expecting:
'f'
ok
Trying:
C1().test_2
Expecting:
'p'
ok
Trying:
C1().test_4()
Expecting:
'c'
ok
2 items had no tests:
__main__
__main__.C1
3 items passed all tests:
1 tests in __main__.C1.test_1
1 tests in __main__.C1.test_2
1 tests in __main__.C1.test_4
3 tests in 5 items.
3 passed and 0 failed.
Test passed.
How can I run test_3 doctest?
Environment
$ python3 --version
Python 3.9.6
$ uname
Darwin

The problem is likely caused by a difference in the implementation of cache and cached_property. Namely, cache sets __module__ whereas cached_property does not:
>>> from functools import cache, cached_property
>>> #cache
... def f(): pass
...
>>> #cached_property
... def g(): pass
...
>>> f.__module__
'__main__'
>>> g.__module__
'functools'
Functions that are not defined in the current __module__ are ignored by doctesting. This is intentional since otherwise all the doctests of the methods that you import at the top of the file would run. However, in this case, this seems like a bug to me.
One can explicitly add a method (or class) to the doctests for a module by adding it to __test__, so in your case this should do the trick:
__test__= { "C1.test_3": C1.test_3 }
To fix this in Python, one should probably add self.__module__ = func.__module__ to this initializer and maybe all the others that update_wrapper() sets for #cache. But maybe this has unintended side effects and that's why this was not set in the first place.

Related

Python unittest: Unable to mock imported functions so that conditional evaluates to False

I'm encountering a problem with unit testing in Python. Specifically, when I try to mock a function my code imports, variables assigned to the output of that function get assigned to a MagicMock object instead of the mock-function's return_value. I've been digging through the docs for python's unittest library, but am not having any luck.
The following is the code I want to test:
from production_class import function_A, function_B, function_M
class MyClass:
def do_something(self):
variable = functionB()
if variable:
do_other_stuff()
else:
do_something_else
this is what I've tried:
#mock.patch(path.to.MyClass.functionB)
#mock.patch(<other dependencies in MyClass>)
def test_do_something(self, functionB_mock):
functionB_mock.return_value = None # or False, or 'foo' or whatever.
myClass = MyClass()
myClass.do_something()
self.assertTrue(else_block_was_executed)
The issue I have is that when the test gets to variable = functionB in MyClass, the variable doesn't get set to my return value; it gets set to a MagicMock object (and so the if-statement always evaluates to True). How do I mock an imported function such that when executed, variables actually get set to the return value and not the MagicMock object itself?
We'd have to see what import path you're actually using with path.to.MyClass.functionB. When mocking objects, you don't necessarily use the path directly to where the object is located, but the one that the intepreter sees when recursively importing modules.
For example, if your test imports MyClass from myclass.py, and that file imports functionB from production_class.py, the mock path would be myclass.functionB, instead of production_class.functionB.
Then there's the issue that you need additional mocks of MyClass.do_other_stuff and MyClass.do_something_else in to check whether MyClass called the correct downstream method, based on the return value of functionB.
Here's a working example that tests both possible return values of functionB, and whether they call the correct downstream method:
myclass.py
from production_class import functionA, functionB, functionM
class MyClass:
def do_something(self):
variable = functionB()
if variable:
self.do_other_stuff()
else:
self.do_something_else()
def do_other_stuff(self):
pass
def do_something_else(self):
pass
production_class.py
import random
def functionA():
pass
def functionB():
return random.choice([True, False])
def functionM():
pass
test_myclass.py
import unittest
from unittest.mock import patch
from myclass import MyClass
class MyTest(unittest.TestCase):
#patch('myclass.functionB')
#patch('myclass.MyClass.do_something_else')
def test_do_something_calls_do_something_else(self, do_something_else_mock, functionB_mock):
functionB_mock.return_value = False
instance = MyClass()
instance.do_something()
do_something_else_mock.assert_called()
#patch('myclass.functionB')
#patch('myclass.MyClass.do_other_stuff')
def test_do_something_calls_do_other_stuff(self, do_other_stuff_mock, functionB_mock):
functionB_mock.return_value = True
instance = MyClass()
instance.do_something()
do_other_stuff_mock.assert_called()
if __name__ == '__main__':
unittest.main()
calling python test_myclass.py results in:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
What I wound up doing was changing the import statements in MyClass to import the object instead of the individual methods. I was then able to mock the object without any trouble.
More explicitly I changed MyClass to look like this:
import production_class as production_class
class MyClass:
def do_something(self):
variable = production_class.functionB()
if variable:
do_other_stuff()
else:
do_something_else
and changed my test to
#mock.patch(path.to.MyClass.production_class)
def test_do_something(self, prod_class_mock):
prod_class_mock.functionB.return_value = None
myClass = MyClass()
myClass.do_something()
self.assertTrue(else_block_was_executed)

How to Mock Instance Method in Python

Consider the following three files.
# my_class.py
class MyClass:
def __init__(self):
pass
def do_thing(self):
return 5
# main.py
from my_class import MyClass
def my_func():
instance = MyClass()
instance.do_thing()
# test_main.py
from main import my_func
from unittest.mock import patch
#patch('main.MyClass')
def test_my_func(MockMyClass):
my_func()
MockMyClass.do_thing.assert_called_once()
AssertionError: Expected 'do_thing' to have been called once. Called 0 times.
I'm instantiating a class MyClass inside a driver function my_func and calling one of the class's methods do_thing. What I'd like to do is test that when the driver function is invoked, the method of the class is called exactly once. I'm encountering an assertion error that's giving me problems.
I've read a million and one SO posts and other resources online about Python mocks, but I'm not able to figure this out. I thought the trick was that the #patch decorator patches the namespace the module is imported into, not from [Python Mocking a function from an imported module. What am I doing wrong here?
The do_thing method is an instance method of MyClass, NOT class method. You assert MockMyClass.do_thing.assert_called_once() is not correct. Here is the unit test solution:
my_class.py:
class MyClass:
def __init__(self):
pass
def do_thing(self):
return 5
main.py:
from my_class import MyClass
def my_func():
instance = MyClass()
instance.do_thing()
test_main.py:
from main import my_func
import unittest
from unittest.mock import patch
class TestMain(unittest.TestCase):
#patch('main.MyClass')
def test_my_func(self, MockMyClass):
mock_my_class_instance = MockMyClass.return_value
my_func()
mock_my_class_instance.do_thing.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test results with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/60539392/main.py 4 0 100%
src/stackoverflow/60539392/my_class.py 5 2 60% 3, 6
src/stackoverflow/60539392/test_main.py 10 0 100%
-----------------------------------------------------------------------
TOTAL

Test function with lru_cache decorator

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)

Parameterizing tests with pytest

I am learning about parameterized tests with pyest. After following the relevant pytest documentation, I came up with this simple example:
import unittest
import pytest
#pytest.fixture(autouse=True, params=['foo', 'bar'])
def foo(request):
print('fixture')
print(request.param)
class Foo(unittest.TestCase):
def setUp(self):
print('unittest setUp()')
def test(self):
print('test')
This gives the following error:
Failed: The requested fixture has no parameter defined for the current test.
E
E Requested fixture 'foo' defined in:
E tests/fixture.py:7
Line 7 is def foo(request):.
What causes this error and how do I fix it?
The goal of fixtures is to pass objects to the test cases, but the fixture you've made doesn't return or yield anything.
Then I'm not sure you can pass objects to a unittest TestCase method, i think it may create some conflicts with the self parameter.
On the other side, it can work with a simple function :
#pytest.fixture(autouse=True, params=['foo', 'bar'])
def foo(request):
print('fixture')
print(request.param)
yield request.param
# class Foo(unittest.TestCase):
# def setUp(self):
# print('unittest setUp()')
#
# def _test(self):
# print('test')
def test_fixture(foo):
assert foo == 'foo'
>>> 1 failed, 1 passed in 0.05 seconds
# test 1 run with foo : OK
# test 2 run with bar : FAILED
EDIT :
Indeed : Why cant unittest.TestCases see my py.test fixtures?

Python mock object instantiation

Using Python 2.7, and mock library
How can I test that certain patched object has been initialized with some specific arguments using mock?
Here some sample code and pseudo-code:
unittest.py :
import mock
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
test1 = mock_someobject.return_value
test1 = method_inside_someobject.side_effect = ['something']
mylib.method_to_test()
# How can I assert that method_to_test instanced SomeObject with certain arguments?
# I further test things with that method_inside_someobject call, no problems there...
mylib.py :
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1=val1, arg2=val2, arg3=val3)
obj.method_inside_someobject()
So, how can I test SomeObject was instanced with arg1=val1, arg2=val2, arg3=val3?
If you replaced a class with a mock, creating an instance is just another call. Assert that the right parameters have been passed to that call, for example, with mock.assert_called_with():
mock_someobject.assert_called_with(arg1=val1, arg2=val2, arg3=val3)
To illustrate, I've updated your MCVE to a working example:
test.py:
import mock
import unittest
import mylib
class TestMyLib(unittest.TestCase):
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
mock_instance = mock_someobject.return_value
mock_instance.method_inside_someobject.side_effect = ['something']
retval = mylib.method_to_test()
mock_someobject.assert_called_with(arg1='foo', arg2='bar', arg3='baz')
self.assertEqual(retval, 'something')
if __name__ == '__main__':
unittest.main()
mylib.py:
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1='foo', arg2='bar', arg3='baz')
return obj.method_inside_someobject()
someobjectmodule.py:
class SomeObject(object):
def method_inside_someobject(self):
return 'The real thing'
and running the test:
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Categories

Resources