Mocking instantiated object vs. class in Python - python

Could someone help me understand the difference between unitest.patch instantianed objects and classes? I'm trying to mock calls to sagemaker.Session.describe_training_job.
I have a function that calls my_session = sagemaker.Session() then calls my_session.describe_training_job() later:
#my_module.py
import sagemaker
def do_something_with_session(local_session, job):
return local_session.describe_training_job(job)
def my_function(args):
my_session = sagemaker.Session()
description = do_something_with_session(my_session, args.job)
If I use the test below, the my_session object is a MagicMock as expected, but does not have the describe_training_job method:
#patch("sagemaker.Session")
def test_class(self, sagemaker_session):
sagemaker_session.describe_training_job = MagicMock(return_value=self.mock_return_value)
my_module.my_function(args=self.mock_args)
If I instead do this:
#patch("sagemaker.Session")
def test_class(self, sagemaker_session):
# Need to instantiate an object for this to work
session_object = sagemaker_session()
session_object.describe_training_job = MagicMock(return_value=self.mock_return_value)
my_module.my_function(self.mock_args)
Then the test works as expected: the my_session object is a MagicMock with a describe_training_job method that always returns the value I set it to.
Could someone help understand what the behavior is here? What I've noticed when is that the name parameter of the MagicMock is Session when I try to use the class, but Session() when I do instantiate the object. Not sure how that affects binding.

A mock is a placeholder object, it does not have the mocked methods (though it may know which methods can be called if using autospec). Every method call on the mock that you do not explicitly provide just returns another mock object.
If you mock a class, and that class is instantiated in the tested code, the resulting instance will be another mock. The instantation is just a __call__ for the mock, which returns another mock as every call does (and it always returns the same mock for the same call). Similar to the result of a function call, this mock can be accessed via return_value.
If you set an attribute on the class mock like in your first example, it is only bound to that class mock, not to the instance mock, so this allows to mock only class methods. The main point to understand here is that the class mock and the instance mock are not related like the class and the instance they are mocking. Both have the same Mock type, and the instance mock doesn't know the class mock that has created it.
This means that instance method attributes have always to be set on the instance mock, as you have done in your second example. Using return_value is eqivalent to that, so you could also write:
sagemaker_session.return_value.describe_training_job = MagicMock(return_value=self.mock_return_value)

Related

How do I patch a python #classmethod to call my side_effect method?

The following code shows the problem.
I can successfully patch object instance and static methods of this SomeClass
However, I can't seem to be able to patch classmethods.
Help much appreciated!
from contextlib import ExitStack
from unittest.mock import patch
class SomeClass:
def instance_method(self):
print("instance_method")
#staticmethod
def static_method():
print("static_method")
#classmethod
def class_method(cls):
print("class_method")
# --- desired patch side effect methods ----
def instance_method(self):
print("mocked instance_method")
def static_method():
print("mocked static_method")
def class_method(cls):
print("mocked class_method")
# --- Test ---
obj = SomeClass()
with ExitStack() as stack:
stack.enter_context(
patch.object(
SomeClass,
"instance_method",
side_effect=instance_method,
autospec=True
)
)
stack.enter_context(
patch.object(
SomeClass,
"static_method",
side_effect=static_method,
# autospec=True,
)
)
stack.enter_context(
patch.object(
SomeClass,
"class_method",
side_effect=class_method,
# autospec=True
)
)
# These work
obj.instance_method()
obj.static_method()
# This fails with TypeError: class_method() missing 1 required positional argument: 'cls'
obj.class_method()
General solution
A way to patch a classmethod would be to use new=classmethod(class_method) instead of side_effects=class_method.
This works pretty well in general.
Downside
Using new, the patched object isn't necessarily an instance of Mock, MagicMock, AsyncMock or PropertyMock anymore (During the rest of the answer i'll only reference Mock as all the others are subclasses of it).
It is only then an instance of these when you explicitly specify it to be one via e.g. new=Mock(...) or ommit the attribute completely.
That wouldn't be the case with the solution provided at the top of this answer.
So when you try to e.g. check if the function already got called using obj.class_method.assert_called(), it'll give an error saying that function has no attribute assert_called which is caused by the fact that the patched object isn't an instance of Mock, but instead a function.
Unfortunately I don't see any solution to this downside in that scenario at the moment
Concluded differences between new and side_effect:
new specifies what object to patch the target with (doesn't necessarily have to be an instance of Mock)
side_effect specifies the side_effect of the Mock instance that gets created when using patch without new
Also they don't play very well together, so only one of these can/should be used in the same patch(...).

What does the instance argument in Python's create_autospec do?

I'm playing around with mock autospecs in Python. Here's a basic test case where I'm autospecing the Django User class using create_autospec.
from unittest.mock import create_autospec
from django.contrib.auth.models import User
def test_mock_spec():
user = create_autospec(User, spec_set=True, instance=True, username="batman")
assert user.username == "batman"
with pytest.raises(AttributeError):
create_autospec(User, spec_set=True, x1=1)
with pytest.raises(AttributeError):
assert user.x2
The test passes when I set both instance=True and instance=False, so what exactly does this parameter do? What's its purpose? I've seen multiple blog posts set it to True (here and here) so I feel like it's important.
The documentation says the following but it doesn't make sense to me:
If a class is used as a spec then the return value of the mock (the instance of the class) will have the same spec. You can use a class as the spec for an instance object by passing instance=True. The returned mock will only be callable if instances of the mock are callable.
Consider mocking the int class. int is callable, like most classes, so a mock of the int class should also be callable.
On the other hand, consider mocking an int instance. Integers are not callable, so a mock of an integer should also not be callable.
The instance argument lets you control which of these behaviors you get. create_autospec(int, instance=False) returns a callable mock, while create_autospec(int, instance=True) returns a non-callable mock. If you do
m1 = create_autospec(int, instance=False)
m2 = create_autospec(int, instance=True)
m1()
m2()
only the m2() line will raise an exception.

How do I mock a class instance and assign attributes to that instance using mocker.patch.object() and pytest-mock?

I am trying to unit test a module (using pytest and pytest-mock) and I would like to create a mocked instance of a class with certain values for its attributes to be used in a #pytest.fixture
I think I've finally found what I'm looking for in patch.object and autospeccing but I don't understand what the arguments are supposed to be, specifically 'attribute'
patch.object(target, attribute, new=DEFAULT, spec=None, create=False,
spec_set=None, autospec=None, new_callable=None, **kwargs)
patch the named member (attribute) on an object (target) with a mock object.
I tried searching for patch.object() examples but I'm only seeing use cases related to mocking the return values for methods in a class.
Like in this example, a method is passed as the 'attribute' argument to patch.object().
Any help or hints in the right direction would be greatly appreciated!
An example would be the method that you're trying to test.
So, if I wanted to check to see if MyClass.method_a was called, I would do:
from .functions import MyClass # import MyClass from wherever
# it is used in the module.
with patch.object(MyClass, 'method_a') as mocked_myclass:
function_that_called_myclass_method_a()
mocked_myclass.assert_called()

Python mock - Check if methods are called in mocked object

I have a certain piece of code that looks like this:
# file1.py
from module import Object
def method():
o = Object("param1")
o.do_something("param2")
I have unittests that look like this:
#patch("file1.Object")
class TestFile(unittest.TestCase):
def test_call(self, obj):
...
I can do obj.assert_called_with() in the unittest to verify that the constructor was called with certain parameters. Is it possible to verify that obj.do_something was called with certain parameters? My instinct is no, as the Mock is fully encapsulated within Object, but I was hoping there might be some other way.
You can do this, because the arguments are passed to the mock object.
This should work:
#patch("file1.Object")
class TestFile:
def test_call(self, obj):
method()
obj.assert_called_once_with("param1")
obj.return_value.do_something.assert_called_once_with("param2")
obj.return_value is the Object instance (which is a MagickMock object with the Object spec), and do_something is another mock in that object that is called with the given parameter.
As long as you are just passing arguments to mock objects, the mock will record this and you can check it. What you don't have is any side effects from the real function calls - so if the original do_something would call another function, this cannot be checked.
When you mock an object, it will mock the methods inside the object aswell. Therefore you are able to see if obj.do_something has been called with certain parameters as obj.do_something.assert_called_with()
For more information regarding unittest mocking can be found at the python library wiki https://docs.python.org/3/library/unittest.mock.html
A perfect example of what you are asking exist within that wiki source:
>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with
Regards, I see that you placed the the patching on the object, try placing it on the function instead, like:
class TestFile(unittest.TestCase):
#patch("file1.Object")
def test_call(self, obj):
...

Test if a python instance method is the same function as a provided by a class

I have a variable that points to a specific class instance's method.
Lets say there is a class called Client that implements a method get.
A single instance called client is created for Client, and a variable get_func is assigned with client's get.
For example, lets assume I have the following simplified code:
class Client:
def get(self):
print("This is the get function!")
client = Client()
get_func = client.get
I have little actual control over get_func and how it's used, I cannot change that.
I would now want to make sure get_func has the Client.get.
The trivial test get_func == Client.get does not work, as get_func is a bound method of a specific Client instance.
I also cannot get the client instance directly (but a way to get the self of a bound method is a valid option, if only I knew how to do that)
Client.get is either a function object (Python 3), or an unbound method (Python 2). Client().get on the other hand, is a bound method. Methods are wrappers around a function object, recording the instance they are bound to (if there is an instance) to pass in as the self argument. See the Python descriptor How-to as to how Python produces methods from functions.
You can unwrap both a bound method and an unbound method to get the underlying function object that they wrap, and test that, with the __func__ attribute:
get_func.__func__ is Client.get # Python 3
get_func.__func__ is Client.get.__func__ # Python 2, unwrap the unbound method
If you need to make your code compatible with both Python 2 and 3, you could create a simple helper function:
def unwrap_method(f):
return getattr(f, '__func__', f)
and use that:
unwrap_method(get_func) is unwrap_method(Client.get)

Categories