Pytest - mocking class instance passed as an argument - python

Let's say I have a simplified class Object:
class Object:
def __init__(self, data):
self.data = data
def get_data():
return data.copy()
And freestanding function foo:
def foo(obj: Object):
copied_data = obj.get_data()
...
I want to test foo and use a fixture with mocked Object instance to pass as the argument to foo. I want the mocked object to return some predefined data so I need to mock its method as well.
How should I do this in a "proper" way with pytest? I'm not sure how to combine mocks and fixtures.

Using the with statement and the sample patching documentations:
As well as a decorator patch() can be used as a context manager in a with statement:
...
>>> class Class:
... def method(self):
... pass
...
>>> with patch('__main__.Class') as MockClass:
... instance = MockClass.return_value
... instance.method.return_value = 'foo'
... assert Class() is instance
... assert Class().method() == 'foo'
...
We can use patch() inside a fixture and then yield the mocked instance.
src.py
class Object:
def __init__(self, data):
print("Real object initialized")
self.data = data
def get_data(self):
print("Real object get_data")
return self.data.copy()
def foo(obj: Object):
print("Object instance:", obj)
copied_data = obj.get_data()
return copied_data
test_src.py
from unittest.mock import patch
import pytest
from src import foo, Object
#pytest.fixture
def object_instance():
with patch('src.Object') as MockClass:
instance = MockClass.return_value
instance.get_data.return_value = 'bar'
yield instance
def test_real_impl():
object_instance = Object([1, 2])
assert foo(object_instance) == [1, 2]
def test_mock_impl(object_instance):
assert foo(object_instance) == 'bar'
Output:
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
_____________________________________________________________________________________________ test_real_impl ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Real object initialized
Object instance: <src.Object object at 0x7fb59ef83820>
Real object get_data
_____________________________________________________________________________________________ test_mock_impl ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Object instance: <MagicMock name='Object()' id='140418032072736'>
2 passed in 0.06s
As you can see, we are able to create a mocked Object and define the return value of its methods.

Related

How to mock a class with nested properties and autospec?

I'm wondering if it's possible to mock a class which contains properties by using patch and autospec? The goal in the example below is to mock (recursively) ClassB.
Example:
# file: class_c.py
class ClassC:
def get_default(self) -> list[int]:
return [1, 2, 3]
def delete(self, name: str):
print(f"delete {name}")
# ----------------------
# file: class_b.py
from class_c import ClassC
class ClassB:
def __init__(self) -> None:
self._ds = ClassC()
#property
def class_c(self) -> ClassC:
return self._ds
def from_config(self, cred: str) -> str:
return cred
# ----------------------
# file: class_a.py
from class_b import ClassB
class ClassA:
def __init__(self):
self._client = ClassB()
#property
def class_b(self) -> ClassB:
return self._client
def test(self, cred: str) -> str:
return cred
# ----------------------
# file: test.py
import pytest
from unittest import mock
#mock.patch("class_a.ClassB", autospec=True)
def test_class_a(_):
class_a = ClassA()
with pytest.raises(TypeError):
class_a.class_b.from_config() # ✅ raised - missing 1 required positional argument: 'cred'
with pytest.raises(TypeError):
class_a.class_b.class_c.delete() # <- ❌ Problem - should raise exception since autospec=True
The property class_c of the class ClassB is not mocked properly. I would expect TypeError when trying to call delete() without any argument
I've tried several things but without success. Any idea?
EDIT:
The code is just an example, and the test function was just written to demonstrate the expected behaviour. ClassB can be seen as a third-party service which needs to be mocked.
EDIT2:
Additionally to the accepted answer, I would propose to use PropertyMock for mocking properties:
def test_class_a():
class_b_mock = create_autospec(class_a.ClassB)
class_c_mock = create_autospec(class_b.ClassC)
type(class_b).class_c = PropertyMock(return_value=class_c_mock)
with mock.patch("class_a.ClassB", return_value=class_b_mock):
class_a_instance = ClassA()
with pytest.raises(TypeError):
class_a_instance.class_b.from_config()
with pytest.raises(TypeError):
class_a_instance.class_b.class_c.delete()
Once you patched the target class, anything you try to access under that class will be mocked with MagicMock (also recursively). Therefore, if you want to keep the specification of that class, then yes, you should use the autospec=true flag.
But because you are trying to mock a class within a class accessed by a property,
You have to keep the specification of each class you want to test:
def test_class_a():
class_b_mock = create_autospec(class_a.ClassB)
class_c_mock = create_autospec(class_b.ClassC)
class_b_mock.class_c = class_c_mock
with mock.patch("class_a.ClassB", return_value=class_b_mock):
class_a_instance = ClassA()
with pytest.raises(TypeError):
class_a_instance.class_b.from_config()
with pytest.raises(TypeError):
class_a_instance.class_b.class_c.delete()

how do I set return_value of a mocked class instance when I don't have access to the instance itself?

Suppose I have some function A.foo() that instantiates and uses an instance of B, calling the member function bar on it.
How can I set return_value on a mocked instance of B when I'm testing my A class, given that I don't have access to the instance of B? Maybe some code would illustrate this better:
import unittest
import unittest.mock
import pandas
class A:
def foo(self):
b = B()
return b.bar()
class B:
def bar():
return 1
#unittest.mock.patch("__main__.B")
class MyTestCase(unittest.TestCase):
def test_case_1(self, MockB):
MockB.bar.return_value = 2
a = A()
self.assertEqual(a.foo(), 2)
test_case = MyTestCase()
test_case.test_case_1()
This fails with;
AssertionError: <MagicMock name='B().bar()' id='140542513129176'> != 2
Apparently the line MockB.bar.return_value = 2 didn't modify the return value of the method.
I think you are not initiating the MockB. You can directly mock "main.B.bar":
#unittest.mock.patch("__main__.B.bar")
class MyTestCase(unittest.TestCase):
def test_case_1(self, MockB):
MockB.return_value = 2
a = A()
self.assertEqual(a.foo(), 2)
You have just 1 mistake in your code. Replace this line:
MockB.bar.return_value = 2
To:
MockB.return_value.bar.return_value = 2
And it would work.
I assume the piece of code you pasted is just a toy example. If the class A and B lies on another file e.g. src/somedir/somefile.py, don't forget to patch the full path.
#unittest.mock.patch("src.somedir.somefile.B")
class MyTestCase(unittest.TestCase):
...
Update
To further expand on this, you can see some usage in the docs:
>>> class Class:
... def method(self):
... pass
...
>>> with patch('__main__.Class') as MockClass:
... instance = MockClass.return_value
... instance.method.return_value = 'foo'
... assert Class() is instance
... assert Class().method() == 'foo'
...
So in your case:
MockB.bar.return_value is like calling a static method e.g. print(MockB.bar())
MockB.return_value.bar.return_value is like calling a class/instance method e.g. print(MockB().bar())
To visualize this:
import unittest.mock
class SomeClass:
def method(self):
return 1
#unittest.mock.patch("__main__.SomeClass")
def test_mock(mock_class):
print(mock_class)
print(mock_class.return_value)
mock_class.method.return_value = -10
mock_class.return_value.method.return_value = -20
print(SomeClass.method())
print(SomeClass().method())
test_mock()
$ python3 test_src.py
<MagicMock name='SomeClass' id='140568144584128'>
<MagicMock name='SomeClass()' id='140568144785952'>
-10
-20
As you can see, mock_class.return_value is the one used for instance operations such as SomeClass().method().
You can solve this without mock.patch. Change the foo method to accept a factory for the dependency it should construct (DI).
class A:
def foo(self, b_factory: 'Callable[[], B]' = B):
b = b_factory()
return b.bar()
def normal_code():
a = A()
assert a.foo() == ...
def test():
dummy_b = ... # build a dummy object here however you like
a = A()
assert a.foo(b_factory=lambda: dummy_b) == 2

python standalone function return object instance that called it

I have a very strange problem.
I need to return a class object to call a function that is supposed to return the class object that called it. I know, I know. Just think of it as a contrived exercise, though for me it is a very real need.
def baz():
# return the object instance that calls me.
class Foo():
def bar(self, func):
return func() # should return the Foo object but how???
new_foo = Foo()
new_foo_instance = new_foo.bar(baz)
is it possible to write anything in baz() that will return the object that called it?
EDIT:
to answer the comments:
I have tried to use inspect, but with no success, I even looked at the entire stack but I cannot find an entry that matches the new_foo object:
new_foo looks like this when I printed it out: <__main__.Foo object at 0x0000029AAFC4C780>
when I printed out the entire stack that entry was not found within it:
def baz():
print(inspect.stack())
return inspect.stack() #[1][3]
>>> [FrameInfo(frame=<frame object at 0x0000029AADB49648>, filename='return_caller.py', lineno=5, function='baz', code_context=[' print(inspect.stack())\n'], index=0), FrameInfo(frame=<frame object at 0x0000029AAD8F0DE8>, filename='return_caller.py', lineno=11, function='bar', code_context=[' return func() # should return the Foo object but how???\n'], index=0), FrameInfo(frame=<frame object at 0x0000029AAD8AC588>, filename='return_caller.py', lineno=19, function='<module>', code_context=['new_foo_instance = new_foo.bar(baz)\n'], index=0)]
So I am not trying to get it to return a new instance of Foo, but actually the exact same instance as new_foo.
Use inspect:
import inspect
def baz():
frame_infos = inspect.stack() # A list of FrameInfo.
frame = frame_infos[1].frame # The frame of the caller.
locs = frame.f_locals # The caller's locals dict.
return locs['self']
class Foo():
def bar(self, func):
return func()
f1 = Foo()
f2 = f1.bar(baz)
print(f1)
print(f2)
print(f2 is f1) # True
Or cheat:
def baz():
return getattr(baz, 'self', None)
class Foo():
def bar(self, func):
func.self = self # Functions can be a place to store global information.
return func()
The above answer is perfect and this is another way to fulfill your need.
import sys
import inspect
def baz():
"""
Return the object instance whose method calls me
"""
for item in dir(sys.modules[__name__]):
elem = eval(item)
if inspect.isclass(elem):
foo_instance = elem()
break
return foo_instance
class Foo():
"""
Foo class
"""
def bar(self, func):
return func() # should return the Foo object but how???
# Instantiation and calling
new_foo = Foo()
new_foo_instance = new_foo.bar(baz)
print(new_foo_instance) # <__main__.Foo object at 0x0000015C5A2F59E8>
print(type(new_foo_instance)) # <class '__main__.Fo
# E:\Users\Rishikesh\Projects\Python3\try>python Get_caller_object_final.py
# <__main__.Foo object at 0x0000015C5A2F59E8>
# <class '__main__.Foo'>
References »
How can I get a list of all classes within current module in Python?
Python: get only classes defined in imported module with dir()?

Class with a registry of methods based on decorators

I have a class that has several methods which each have certain properties (in the sense of quality). I'd like these methods to be available in a list inside the class so they can be executed at once. Note that the properties can be interchangeable so this can't be solved by using further classes that would inherit from the original one. In an ideal world it would look something like this:
class MyClass:
def __init__():
red_rules = set()
blue_rules = set()
hard_rules = set()
soft_rules = set()
#red
def rule_one(self):
return 1
#blue
#hard
def rule_two(self):
return 2
#hard
def rule_three(self):
return 3
#blue
#soft
def rule_four(self):
return 4
When the class is instantiated, it should be easy to simply execute all red and soft rules by combining the sets and executing the methods. The decorators for this are tricky though since a regular registering decorator can fill out a global object but not the class attribute:
def red(fn):
red_rules.add(fn)
return fn
How do I go about implementing something like this?
You can subclass set and give it a decorator method:
class MySet(set):
def register(self, method):
self.add(method)
return method
class MyClass:
red_rules = MySet()
blue_rules = MySet()
hard_rules = MySet()
soft_rules = MySet()
#red_rules.register
def rule_one(self):
return 1
#blue_rules.register
#hard_rules.register
def rule_two(self):
return 2
#hard_rules.register
def rule_three(self):
return 3
#blue_rules.register
#soft_rules.register
def rule_four(self):
return 4
Or if you find using the .register method ugly, you can always define the __call__ method to use the set itself as a decorator:
class MySet(set):
def __call__(self, method):
"""Use set as a decorator to add elements to it."""
self.add(method)
return method
class MyClass:
red_rules = MySet()
...
#red_rules
def rule_one(self):
return 1
...
This looks better, but it's less explicit, so for other collaborators (or future yourself) it might be harder to grasp what's happening here.
To call the stored functions, you can just loop over the set you want and pass in the instance as the self argument:
my_instance = MyClass()
for rule in MyClass.red_rules:
rule(my_instance)
You can also create an utility function to do this for you, for example you can create a MySet.invoke() method:
class MySet(set):
...
def invoke(self, obj):
for rule in self:
rule(obj)
And now just call:
MyClass.red_rules.invoke(my_instance)
Or you could have MyClass handle this instead:
class MyClass:
...
def invoke_rules(self, rules):
for rule in rules:
rule(self)
And then call this on an instance of MyClass:
my_instance.invoke_rules(MyClass.red_rules)
Decorators are applied when the function is defined; in a class that's when the class is defined. At this point in time there are no instances yet!
You have three options:
Register your decorators at the class level. This is not as clean as it may sound; you either have to explicitly pass additional objects to your decorators (red_rules = set(), then #red(red_rules) so the decorator factory can then add the function to the right location), or you have to use some kind of class initialiser to pick up specially marked functions; you could do this with a base class that defines the __init_subclass__ class method, at which point you can iterate over the namespace and find those markers (attributes set by the decorators).
Have your __init__ method (or a __new__ method) loop over all the methods on the class and look for special attributes the decorators have put there.
The decorator would only need to add a _rule_name or similar attribute to decorated methods, and {getattr(self, name) for for name in dir(self) if getattr(getattr(self, name), '_rule_name', None) == rule_name} would pick up any method that has the right rule name defined in rule_name.
Make your decorators produce new descriptor objects; descriptors have their __set_name__() method called when the class object is created. This gives you access to the class, and thus you can add attributes to that class.
Note that __init_subclass__ and __set_name__ require Python 3.6 or newer; you'd have to resort to a metaclass to achieve similar functionality in earlier versions.
Also note that when you register functions at the class level, that you need to then explicitly bind them with function.__get__(self, type(cls)) to turn them into methods, or you can explicitly pass in self when calling them. You could automate this by making a dedicated class to hold the rule sets, and make this class a descriptor too:
import types
from collections.abc import MutableSet
class RulesSet(MutableSet):
def __init__(self, values=(), rules=None, instance=None, owner=None):
self._rules = rules or set() # can be a shared set!
self._instance = instance
self._owner = owner
self |= values
def __repr__(self):
bound = ''
if self._owner is not None:
bound = f', instance={self._instance!r}, owner={self._owner!r}'
rules = ', '.join([repr(v) for v in iter(self)])
return f'{type(self).__name__}({{{rules}}}{bound})'
def __contains__(self, ob):
try:
if ob.__self__ is self._instance or ob.__self__ is self._owner:
# test for the unbound function instead when both are bound, this requires staticmethod and classmethod to be unwrapped!
ob = ob.__func__
return any(ob is getattr(f, '__func__', f) for f in self._rules)
except AttributeError:
# not a method-like object
pass
return ob in self._rules
def __iter__(self):
if self._owner is not None:
return (f.__get__(self._instance, self._owner) for f in self._rules)
return iter(self._rules)
def __len__(self):
return len(self._rules)
def add(self, ob):
while isinstance(ob, Rule):
# remove any rule wrappers
ob = ob._function
assert isinstance(ob, (types.FunctionType, classmethod, staticmethod))
self._rules.add(ob)
def discard(self, ob):
self._rules.discard(ob)
def __get__(self, instance, owner):
# share the set with a new, bound instance.
return type(self)(rules=self._rules, instance=instance, owner=owner)
class Rule:
#classmethod
def make_decorator(cls, rulename):
ruleset_name = f'{rulename}_rules'
def decorator(f):
return cls(f, ruleset_name)
decorator.__name__ = rulename
return decorator
def __init__(self, function, ruleset_name):
self._function = function
self._ruleset_name = ruleset_name
def __get__(self, *args):
# this is mostly here just to make Python call __set_name__
return self._function.__get__(*args)
def __set_name__(self, owner, name):
# register, then replace the name with the original function
# to avoid being a performance bottleneck
ruleset = getattr(owner, self._ruleset_name, None)
if ruleset is None:
ruleset = RulesSet()
setattr(owner, self._ruleset_name, ruleset)
ruleset.add(self)
# transfer controrol to any further rule objects
if isinstance(self._function, Rule):
self._function.__set_name__(owner, name)
else:
setattr(owner, name, self._function)
red = Rule.make_decorator('red')
blue = Rule.make_decorator('blue')
hard = Rule.make_decorator('hard')
soft = Rule.make_decorator('soft')
Then just use:
class MyClass:
#red
def rule_one(self):
return 1
#blue
#hard
def rule_two(self):
return 2
#hard
def rule_three(self):
return 3
#blue
#soft
def rule_four(self):
return 4
and you can access self.red_rules, etc. as a set with bound methods:
>>> inst = MyClass()
>>> inst.red_rules
RulesSet({<bound method MyClass.rule_one of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.blue_rules
RulesSet({<bound method MyClass.rule_two of <__main__.MyClass object at 0x106fe7550>>, <bound method MyClass.rule_four of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.hard_rules
RulesSet({<bound method MyClass.rule_three of <__main__.MyClass object at 0x106fe7550>>, <bound method MyClass.rule_two of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> inst.soft_rules
RulesSet({<bound method MyClass.rule_four of <__main__.MyClass object at 0x106fe7550>>}, instance=<__main__.MyClass object at 0x106fe7550>, owner=<class '__main__.MyClass'>)
>>> for rule in inst.hard_rules:
... rule()
...
2
3
The same rules are accessible on the class; normal functions remain unbound:
>>> MyClass.blue_rules
RulesSet({<function MyClass.rule_two at 0x107077a60>, <function MyClass.rule_four at 0x107077b70>}, instance=None, owner=<class '__main__.MyClass'>)
>>> next(iter(MyClass.blue_rules))
<function MyClass.rule_two at 0x107077a60>
Containment testing works as expected:
>>> inst.rule_two in inst.hard_rules
True
>>> inst.rule_two in inst.soft_rules
False
>>> MyClass.rule_two in MyClass.hard_rules
True
>>> MyClass.rule_two in inst.hard_rules
True
You can use these rules to register classmethod and staticmethod objects too:
>>> class Foo:
... #hard
... #classmethod
... def rule_class(cls):
... return f'rule_class of {cls!r}'
...
>>> Foo.hard_rules
RulesSet({<bound method Foo.rule_class of <class '__main__.Foo'>>}, instance=None, owner=<class '__main__.Foo'>)
>>> next(iter(Foo.hard_rules))()
"rule_class of <class '__main__.Foo'>"
>>> Foo.rule_class in Foo.hard_rules
True

How to unit test a method called within __init__() function?

I am trying to test a class method which is called within an__init__ function.
class abc:
def __init__(path):
list = []
foo(path)
bar('hello') # test function bar
def foo(self, path):
# does a bunch of stuff and creates internal list
list =
def bar(self):
# does a bunch of stuff and uses list
I would like to write a test for method bar here which I guess must be called through an instance of class abc. I can mock list array for this test, but cannot understand how to avoid the call to foo().
Just mock foo method for the time of testing bar. You can use patch.object.
A full example below:
import unittest
from unittest.mock import patch
class MyClass:
def __init__(self, path):
self.list = []
self.foo(path)
self.bar('/init')
def foo(self, path):
self.list.append(path)
def bar(self, path):
self.list.insert(0, path)
class MyTestClass(unittest.TestCase):
#patch.object(MyClass, 'foo')
def test_bar_decorated(self, mock):
a = MyClass('/foo')
a.bar('/bar')
self.assertEqual(a.list, ['/bar', '/init']) # .foo() wasn't invoked
if __name__ == '__main__':
unittest.main()
Notice that, a mock is created for you and passed in as an extra argument to the decorated function (we don't use it in this test). To avoid that you can use context manager version of patch.object:
def test_bar_context_manager(self):
with patch.object(MyClass, 'foo'):
a = MyClass('/foo')
a.bar('/bar')
self.assertEqual(a.list, ['/bar', '/init']) # same behaviour

Categories