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

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.

Related

How to employ a MagicMock spec_set or spec on a method?

I am trying to use a method as a spec_set on a MagicMock.
https://stackoverflow.com/a/25323517/11163122 provides a nice example of an unexpected call signature resulting in an Exception. Unfortunately, I can't get it to work out for a method.
How can I get the below code snippet to error out, given the calls don't match the spec?
from unittest.mock import MagicMock
class Foo:
def bar(self) -> None:
"""Some method I want to use as a spec."""
mocking_foo_bar_1 = MagicMock(name="with spec", spec=Foo.bar)
mocking_foo_bar_2 = MagicMock(name="with spec_set", spec_set=Foo.bar)
# These aren't raising, even though they don't match the spec
mocking_foo_bar_1("this", "should", "raise")
mocking_foo_bar_2("this", "also", "should", "raise")
I am using Python 3.8+.
spec and spec_set are used for limiting the set of attributes a Mock is allowed to access. For functions, there's create_autospec.
Functions or methods being mocked will have their arguments checked to ensure that they are called with the correct signature.
Since bar is an instance method of Foo, you will also need to create an instance of Foo to get the expected function signature.
from unittest.mock import create_autospec
mocking_foo_bar = create_autospec(Foo().bar)
mocking_foo_bar("this", "should", "raise")
This raises an error:
TypeError: too many positional arguments

How to spec a Python mock which defaults to AttributeError() without explicit assignment?

I have a question related to Python unittest.mock.Mock and spec_set functionalities.
My goal is to create a Mock with the following functionalities:
It has a spec of an arbitrary class I decide at creation time.
I must be able to assign on the mock only attributes or methods according to the spec of point 1
The Mock must raise AttributeError in the following situations:
I try to assign an attribute that is not in the spec
I call or retrieve a property that is either missing in the spec_set, or present in the spec_set but assigned according to the above point.
Some examples of the behavior I would like:
class MyClass:
property: int = 5
def func() -> int:
pass
# MySpecialMock is the Mock with the functionalities I am dreaming about :D
mock = MyMySpecialMock(spec_set=MyClass)
mock.not_existing # Raise AttributeError
mock.func() # Raise AttributeError
mock.func = lambda: "it works"
mock.func() # Returns "it works"
I have tried multiple solutions without any luck, or without being explicitly verbose. The following are some examples:
Using Mock(spec_set=...), but it does not raise errors in case I call a specced attribute which I did not explicitly set
Using Mock(spec_set=...) and explicitly override every attribute with a function with an Exception side effect, but it is quite verbose since I must repeat all the attributes...
My goal is to find a way to automatize 2, but I have no clean way to do so. Did you ever encounter such a problem, and solve it?
For the curious ones, the goal is being able to enhance the separation of unit testings; I want to be sure that my mocks are called only on the methods I explicitly set, to avoid weird and unexpected side effects.
Thank you in advance!
spec_set defines a mock object which is the same as the class, but then doesn't allow any changes to be made to it, since it defines special __getattr__ and __setattr__. This means that the first test (calling a non-existent attr) will fail as expected, but then so will trying to set an attr:
from unitest import mock
class X:
pass
m = mock.Mock(spec_set=X)
m.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
m.func = lambda: "it works"
# __setattr__: AttributeError: Mock object has no attribute 'func'
Instead, you can use create_autospec() which copies an existing function, and adds the mock functions to it, but without affecting __setattr__:
n = mock.create_autospec(X)
n.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
n.func = lambda: "it works"
n.func()
# 'it works'
I think I found a satisfying answer to my problem, by using the dir method.
To create the Mock with the requirements I listed above, it should be enough to do the following:
def create_mock(spec: Any) -> Mock:
mock = Mock(spec_set=spec)
attributes_to_override = dir(spec)
for attr in filter(lambda name: not name.startswith("__"), attributes_to_override):
setattr(mock, attr, Mock(side_effect=AttributeError(f"{attr} not implemented")))
return mock

Mocking instantiated object vs. class in 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)

How to mock a Python class during testing for runtime typing checks?

I have some application method that uses the #typeguard.typechecked decorator for performing runtime checks of the passed parameters:
class SalesDeal:
pass
#typechecked
def do_something(deal: SalesDeal):
pass
Inside a test I have fake class FakeSalesDeal which implements a minimal mock for SalesDeal (which is a very complicated class in reality):
class FakeSalesDeal:
pass
def test_foo():
deal = FakeSalesDeal()
do_something(deal)
This test will of course fail because the #typechecked decorator will raise an error due to a different class.
Is there a way to mock/fake the class of FakeSalesDeal in order make the test pass?
You can use MagicMock with spec set to SalesDeal instead of creating a fake class.
isinstance(mock, SalesDeal) will be True for that mock object & you should be able to bypass the typecheck.
from unittest.mock import MagicMock
# ...
def test_foo():
deal = MagicMock(spec=SalesDeal)
print(isinstance(deal, SalesDeal))
do_something(deal)
test_foo()
This prints:
True
& doesn't throw any type check error.
This works because typechecked explicitly checks for Mock objects being passed with:
if expected_type is Any or isinstance(value, Mock):
return
Code from here
So if you use proper mocks, typechecked should not be causing any issues for you.
My final solution:
class FakeSalesDeal(MagicMock):
pass

Python: how does inspect.ismethod work?

I'm trying to get the name of all methods in my class.
When testing how the inspect module works, i extraced one of my methods by obj = MyClass.__dict__['mymethodname'].
But now inspect.ismethod(obj) returns False while inspect.isfunction(obj) returns True, and i don't understand why. Is there some strange way of marking methods as methods that i am not aware of? I thought it was just that it is defined in the class and takes self as its first argument.
You are seeing some effects of the behind-the-scenes machinery of Python.
When you write f = MyClass.__dict__['mymethodname'], you get the raw implementation of "mymethodname", which is a plain function. To call it, you need to pass in an additional parameter, class instance.
When you write f = MyClass.mymethodname (note the absence of parentheses after mymethodname), you get an unbound method of class MyClass, which is an instance of MethodType that wraps the raw function you obtained above. To call it, you need to pass in an additional parameter, class instance.
When you write f = MyClass().mymethodname (note that i've created an object of class MyClass before taking its method), you get a bound method of an instance of class MyClass. You do not need to pass an additional class instance to it, since it's already stored inside it.
To get wrapped method (bound or unbound) by its name given as a string, use getattr, as noted by gnibbler. For example:
unbound_mth = getattr(MyClass, "mymethodname")
or
bound_mth = getattr(an_instance_of_MyClass, "mymethodname")
Use the source
def ismethod(object):
"""Return true if the object is an instance method.
Instance method objects provide these attributes:
__doc__ documentation string
__name__ name with which this method was defined
__func__ function object containing implementation of method
__self__ instance to which this method is bound"""
return isinstance(object, types.MethodType)
The first argument being self is just by convention. By accessing the method by name from the class's dict, you are bypassing the binding, so it appears to be a function rather than a method
If you want to access the method by name use
getattr(MyClass, 'mymethodname')
Well, do you mean that obj.mymethod is a method (with implicitly passed self) while Klass.__dict__['mymethod'] is a function?
Basically Klass.__dict__['mymethod'] is the "raw" function, which can be turned to a method by something called descriptors. This means that every function on a class can be both a normal function and a method, depending on how you access them. This is how the class system works in Python and quite normal.
If you want methods, you can't go though __dict__ (which you never should anyways). To get all methods you should do inspect.getmembers(Klass_or_Instance, inspect.ismethod)
You can read the details here, the explanation about this is under "User-defined methods".
From a comment made on #THC4k's answer, it looks like the OP wants to discriminate between built-in methods and methods defined in pure Python code. User defined methods are of types.MethodType, but built-in methods are not.
You can get the various types like so:
import inspect
import types
is_user_defined_method = inspect.ismethod
def is_builtin_method(arg):
return isinstance(arg, (type(str.find), type('foo'.find)))
def is_user_or_builtin_method(arg):
MethodType = types.MethodType
return isinstance(arg, (type(str.find), type('foo'.find), MethodType))
class MyDict(dict):
def puddle(self): pass
for obj in (MyDict, MyDict()):
for test_func in (is_user_defined_method, is_builtin_method,
is_user_or_builtin_method):
print [attr
for attr in dir(obj)
if test_func(getattr(obj, attr)) and attr.startswith('p')]
which prints:
['puddle']
['pop', 'popitem']
['pop', 'popitem', 'puddle']
['puddle']
['pop', 'popitem']
['pop', 'popitem', 'puddle']
You could use dir to get the name of available methods/attributes/etc, then iterate through them to see which ones are methods. Like this:
[ mthd for mthd in dir(FooClass) if inspect.ismethod(myFooInstance.__getattribute__(mthd)) ]
I'm expecting there to be a cleaner solution, but this could be something you could use if nobody else comes up with one. I'd like if I didn't have to use an instance of the class to use getattribute.

Categories