Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using unittest.mock.patch, why is autospec not True by default?

When you patch a function using mock, you have the option to specify autospec as True:

If you set autospec=True then the mock with be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced. Methods and functions being mocked will have their arguments checked and will raise a TypeError if they are called with the wrong signature.

(http://www.voidspace.org.uk/python/mock/patch.html)

I'm wondering why this isn't the default behaviour? Surely we would almost always want to catch passing incorrect parameters to any function we patch?

like image 281
seddonym Avatar asked Mar 10 '16 11:03

seddonym


People also ask

What is Autospec in mock patch?

When you patch a function using mock, you have the option to specify autospec as True: If you set autospec=True then the mock with be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced.

What does Autospec true do?

In short, using autospec ensures that the attributes you use in your mock are in fact part of the class you are mocking.

How does Unittest mock patch work?

unittest. mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

What is the difference between mock and MagicMock?

Mock vs. So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.


1 Answers

The only clear way to explain this, is to actually quote the documentation on the downside of using auto-speccing and why you should be careful when using it:

This isn’t without caveats and limitations however, which is why it is not the default behaviour. In order to know what attributes are available on the spec object, autospec has to introspect (access attributes) the spec. As you traverse attributes on the mock a corresponding traversal of the original object is happening under the hood. If any of your specced objects have properties or descriptors that can trigger code execution then you may not be able to use autospec. On the other hand it is much better to design your objects so that introspection is safe [4].

A more serious problem is that it is common for instance attributes to be created in the init method and not to exist on the class at all. autospec can’t know about any dynamically created attributes and restricts the api to visible attributes.

I think the key takeaway here is to note this line: autospec can’t know about any dynamically created attributes and restricts the api to visible attributes

So, to help being more explicit with an example of where autospeccing breaks, this example taken from the documentation shows this:

>>> class Something: ...   def __init__(self): ...     self.a = 33 ... >>> with patch('__main__.Something', autospec=True): ...   thing = Something() ...   thing.a ... Traceback (most recent call last):   ... AttributeError: Mock object has no attribute 'a' 

As you can see, auto-speccing has no idea that there is an attribute a being created when creating your Something object.

There is nothing wrong with assigning a value to your instance attribute.

Observe the below functional example:

import unittest from mock import patch  def some_external_thing():     pass  def something(x):     return x  class MyRealClass:     def __init__(self):         self.a = some_external_thing()      def test_thing(self):         return something(self.a)    class MyTest(unittest.TestCase):     def setUp(self):         self.my_obj = MyRealClass()      @patch('__main__.some_external_thing')         @patch('__main__.something')     def test_my_things(self, mock_something, mock_some_external_thing):         mock_some_external_thing.return_value = "there be dragons"         self.my_obj.a = mock_some_external_thing.return_value         self.my_obj.test_thing()          mock_something.assert_called_once_with("there be dragons")   if __name__ == '__main__':     unittest.main() 

So, I'm just saying for my test case I want to make sure that the some_external_thing() method does not affect the behaviour of my unittest, so I'm just assigning my instance attribute the mock per mock_some_external_thing.return_value = "there be dragons".

like image 192
idjaw Avatar answered Oct 02 '22 08:10

idjaw