How to mock a property - python

I'm asking how to mock a class property in a unit test using Python 3. I've tried the following, which makes sense for me following the docs, but it doesn't work:
foo.py:
class Foo():
#property
def bar(self):
return 'foobar'
def test_foo_bar(mocker):
foo = Foo()
mocker.patch.object(foo, 'bar', new_callable=mocker.PropertyMock)
print(foo.bar)
I've installed pytest and pytest_mock and run the test like this:
pytest foo.py
I got the following error:
> setattr(self.target, self.attribute, new_attr)
E AttributeError: can't set attribute
/usr/lib/python3.5/unittest/mock.py:1312: AttributeError
My expectation would be that the test runs without errors.

The property mechanism relies on the property attribute being defined on the object's class. You can't create a "property like" method or attribute on a single instance of a class (for a better understanding, read about Python's descriptor protocol)
Therefore you have to apply the patch to your class - you can use the with statement so that the class is properly restored after your test:
def test_foo_bar(mock):
foo = Foo()
with mock.patch(__name__ + "Foo.bar", new=mocker.PropertyMock)
print(foo.bar)

You can directly return the value if you do not need extra features
from mock import patch
#patch('foo.Foo.bar', 'mocked_property_value')
def test_foo_bar():
foo = Foo()
print(foo.bar)
Or you can wrap MagicMocks with a call to function property:
from mock import patch, MagicMock
#patch('foo.Foo.bar', property(MagicMock(return_value='mocked_property_value')))
def test_foo_bar():
foo = Foo()
print(foo.bar)

Related

AttributeError when using Pytest monkeypatch to change fixture object attribute

In my pytest file test_foo.py, I am trying to change the value of foo.path to mock_path using monkeypatch. However, when I try the following, I get an error
ERROR test_foo.py::test_foo - AttributeError: 'foo' has no attribute 'path'
What should be the correct way to make this change so that foo object in test_foo will use mock_path and pass that test?
test_foo.py:
import os
import pytest
class Foo:
def __init__(self):
self.path = os.path.join(os.getcwd(), "test.txt")
#pytest.fixture
def foo(monkeypatch):
monkeypatch.setattr(Foo, 'path', 'mock_path')
return Foo()
def test_foo(foo):
assert foo.path == "mock_path"
You are trying to change the attribute on the class instead of the class instance, so the error message comes from monkeypath.setattr - Foo has indeed no attribute path, as this is an instance variable.
To fix this, patch the class instance instead:
#pytest.fixture
def foo(monkeypatch):
inst = Foo()
monkeypatch.setattr(inst, 'path', 'mock_path')
return inst

How to patch built-in/extension types in python using mockito?

While using mockito for unit testing in python, I came across an issue that I cannot find a solution for. I am trying to patch the usage of io.BytesIO in a given class method. The following code shows a simplified version where the issue occurs:
from mockito import mock, patch, when
from io import BytesIO
class Foo:
def bar(self):
buffer = io.BytesIO()
# ...
return buffer.getvalue()
def test_foo():
bytesIO_mock = mock(strict=True)
when(bytesIO_mock).getvalue().thenReturn('data')
patch(BytesIO.__new__, lambda: bytesIO_mock)
result = Foo().bar()
assert result == 'data'
I am getting the following error when I execute the test:
/venv/lib/python3.6/site-packages/mockito/mockito.py:270: in patch
when2(fn, Ellipsis).thenAnswer(replacement)
/venv/lib/python3.6/site-packages/mockito/mockito.py:245: in when2
return invocation.StubbedInvocation(theMock, name)(*args, **kwargs)
/venv/lib/python3.6/site-packages/mockito/invocation.py:284: in __call__
self.mock.stub(self.method_name)
/venv/lib/python3.6/site-packages/mockito/mocking.py:117: in stub
self.replace_method(method_name, original_method)
/venv/lib/python3.6/site-packages/mockito/mocking.py:108: in replace_method
self.set_method(method_name, new_mocked_method)
self = <mockito.mocking.Mock object at 0x10d50cb38>, method_name = '__new__'
new_method = <function Mock.replace_method.<locals>.new_mocked_method at 0x10d753e18>
def set_method(self, method_name, new_method):
> setattr(self.mocked_obj, method_name, new_method)
E TypeError: can't set attributes of built-in/extension type '_io.BytesIO'
/venv/lib/python3.6/site-packages/mockito/mocking.py:74: TypeError
Is there any solution to this issue, or is it just the case it is not possible to mock certain objects in python?
Instead of using patch you can simply mock the response from io.BytesIO() as follows:
def test_foo():
bytesIO_mock = mock(strict=True)
when(bytesIO_mock).getvalue().thenReturn('data')
when(io).BytesIO().thenReturn(bytesIO_mock)
result = Foo().bar()
assert result == 'data'
As a rule of thumb, all objects in Python can be mocked as everything in Python is an object. If you can not mock it, it is probably due to a limitation in the testing library/framework being used.

Why does mock ignore the instance/object passed to a mocked out method when it is called?

I have recently noticed that if I mock out a method using mock.patch it doesn't list the instance object passed to the mocked method in the call_args field. Is this by design? Code/output below may better explain what I mean:
#!/usr/bin/env python2
from mock import patch
class Dog(object):
def bark(self):
print("Woof woof!")
Dog().bark()
def new_method(*args):
print("args = %s" % str(args))
Dog.bark = new_method
Dog().bark()
with patch.object(Dog, "bark"):
d = Dog()
d.bark()
print("d.bark was called: %s" % str(d.bark.called))
print("d.bark was called with args/kwargs: %s" % str(d.bark.call_args))
Output is:
Woof woof!
args = (<__main__.Dog object at 0x7f42c2dbc3d0>,)
# Mocking bit
d.bark was called: True
d.bark was called with args/kwargs: ((), {})
You can see that the instance object is passed to new_method when it replaces bark. But you cannot see it in the call_args of the mocked out method. Isn't this strange? I am using version 1.01 of the python mock library.
By
with patch.object(Dog, "bark"):
You are patching the static instance of Dog.bark method because you are patching Dog class and not a Dog object.
Now the mock method will be called as a static method and not as object method: that means the self attribute will not passed.
If you want create a patch with the same signature of original method you can use autospec=True attribute: in this case the mock method will be an object method instead of a static method.
>>> from mock import patch
>>> with patch.object(Dog, "bark", autospec=True):
... d = Dog()
... d.bark()
... print("d.bark was called with args/kwargs: %s" % str(d.bark.call_args))
...
<MagicMock name='bark()' id='139848306278672'>
d.bark was called with args/kwargs: call(<Dog object at 0x7f30f89ef390>)

Unable to mock __subclasses__ in python

Why does the following code not work?
>>> from mock import *
>>> class A(object):
... pass
...
>>> mock = create_autospec(A)
>>> mock.foo = Mock() # this works
>>> mock.__bar__ = Mock() # this works too
>>> mock.__subclasses__ = Mock() # this fails
AttributeError: Mock object has no attribute '__subclasses__'
I think I'm following the documentation on mocking magic methods here. The docs do remark that trying to mock a magic method which is not in the spec will not work. But why would __subclasses__ not be in the spec of an autospecced new-style class?
__subclasses__ is not part of the class spec. It is part of the metatype of the class (type here).
Python always looks up special methods on the type, never directly. If and when Python needs to call __subclasses__, it'll not do so directly, it'll use type(classobj).__subclasses__(classobj) to look up the unbound method and pass in the first argument manually. As such, adding __subclasses__ to the mock of a class is not going to be enough.
The same applies to special methods intended to operate on instances; Mock will happily take __add__ or __str__ along when using create_autospec(), and that works then for instances of the mocked class where Python will use type(mockinstance).__str__(mockinstance) to invoke the __str__ method.
If your own code calls classobj.__subclasses__() directly, you'll need to pass in a class mock that explicitly sets that method; you can't expect it to be auto-specced here.
Mocks don't like you setting any valid magic method:
>>> m = create_autospec(A)
>>> m.__add__ = Mock()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mj/Development/venvs/stackoverflow-2.7/lib/python2.7/site-packages/mock.py", line 767, in __setattr__
raise AttributeError("Mock object has no attribute '%s'" % name)
AttributeError: Mock object has no attribute '__add__'
only non-default magic methods are allowed:
>>> m.__dummy__ = Mock()
>>> m.__dummy__
<Mock name='mock.__dummy__' id='4427608656'>
You can create a subclass of A adding in the __subclass__ method to allow you to mock it:
>>> class AMockSpec(A):
... def __subclasses__(self): pass
...
>>> m = create_autospec(AMockSpec)
>>> m.__subclasses__ = Mock()
>>> m.__subclasses__.return_value = ['SomeMockValue']
>>> m.__subclasses__()
['SomeMockValue']
The following does work (__subclasses__ is available on __class__):
>>> from mock import *
>>> class A(object):
... pass
...
>>> m = create_autospec(A)
>>> m.__class__.__subclasses__()
[]
>>> class B(A): pass
...
>>> m.__class__.__subclasses__()
[<class '__main__.B'>]
>>> m.__class__.__subclasses__ = Mock()
>>> m.__class__.__subclasses__()
<Mock name='mock()' id='4372594896'>

How to mock up a class variable value in python unit test?

Say I have a class:
class A():
def f(self):
self._v = 1
Tried:
m=Mocker()
A.f._v = m.mock()
...
but didn't work. Not sure how...
Did you mean Mock library?
from mock import Mock
real = ProductionClass()
real.method = Mock(return_value=3)
real.method(3, 4, 5, key='value')
edit:
You are trying to access A.f._v before mocking which is impossible.
Not sure what are you trying to do, but this will work
>>>A.f = Mock()
>>>a = A()
>>>a.f._v
<Mock name='mock._v' id='42076240'>
The class definition shows an instance variable to set it from outside this class, do something like this:
class A:
def f(self):
self._v = 1
a = A()
a._v = Mock()
If you actually wanted a real class variable, try this:
class A():
_v = None
def f(self):
self.__class__._v = 1
A._v = Mock()
I tried above solutions and it still does not solve my purpose which is exactly what is asked originally. The above approach would update the mocked attribute of my class to have a value of .
My requirement is to set the attribute value from the mocked value I provide in my unit test class.
I could finally resolved this with the help of following approach. Let me know if its not a correct way:
Actual Class:
class ActualClass(object):
name=''
def some_method(self):
name=get_name_from_external_source() #Say, returned name='ActualValue'
print name
Unit Test Class:
from mock import PropertyMock
import unittest
class TestActualClass(unittest.TestCase):
def test_some_method(self):
actual_class=ActualClass()
p=PropertyMock(return_value='Mocked_Name')
type(actual_class).name=p
actual_class.some_method()
When you run some_method in ActualClass through normal execution, the output:
ActualValue
When you run TestActualClass, the output:
Mocked_Name
This implies that class attributes are mocked with a mocked value using PropertyType and you can test the method with mocked value and without worrying about external source method call.

Categories