AttributeError when using Pytest monkeypatch to change fixture object attribute - python

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

Related

How to define a mock object inside a mock in python?

I have a class that contains another class in a variable. Now I want to write a unit-test and define a mock object. Therefore I define a fixture in conftest.py and monkeypatch it with the mock object. I now get a the desired mock object but the inner object is noch the mock object which I defined. The problem ist that my mock that I have created in the fixture (so that the mock returns "Bye") will not be applied. How can I fix this and is there any better solution? See my minimal example below:
module_a.py
class Connection:
def get(self, name):
return f"Hello {name}"
utils.py
from main.module_a import Connection
class Util:
def __int__(self):
self.conn: Connection = Connection()
module_main.py
from main.utils import Util
def main() -> str:
util: Util = Util()
msg: str = util.conn.get(name="Joe")
return msg
conftest.py
from unittest.mock import Mock
import pytest
from main.module_a import Connection
from main.utils import Util
#pytest.fixture(scope="function")
def util_mock(monkeypatch):
conn_mock: Mock = Mock(spec=Connection)
conn_mock.get.return_value = "Bye"
util_mock: Mock = Mock(spec=Util, conn=conn_mock)
monkeypatch.setattr(target="main.module_main.Util", name=util_mock)
return util_mock
test_module_main.py
from unittest.mock import Mock
from main import module_main
def test_main(util_mock: Mock):
msg: str = module_main.main()
test: str = util_mock.conn.get(name="foot")
assert test == "Bye" # work right afer fixture insertion
assert msg == "Bye" # fails after a new object is created
Found a solution myself. When a new object is created (Util()) __call__ is triggered and returns a new object of the mock, hence all defined properties are lost. We just need to return the mock object itself with util_mock.return_value = util_mock.

Unable to patch an object's attribute correctly

I have a Python module as follows:
# src/exec.py
class A:
def run(self, stuff):
b = B(stuff.x)
class B:
def __init__(self, x):
self.obj = self.create_some_obj()
I'm trying to test a part of class A independently, for which I need to replace the obj in B with a fake object. I'm doing this as follows:
# test/test_execs.py
import exec as ex
class FakeObjForB:
def __init__(self):
# some init
class TestClass:
#patch.object(ex.B, 'obj', FakeObjForB())
def test_with_fake_obj(self):
a = ex.A()
a.run()
# assert something about the state of a that depends on the b inside its run method
Running this test gives me the error: AttributeError: <class 'B'> does not have the attribute 'obj'. I tried replacing the line with the #patch decorator with #patch.object(ex.B, 'obj', FakeObjForB(), create=True). This, however, results in b.obj using the actual definition, and not FakeObjForB, which in turn leads to a false-failure in the assertion in test_with_fake_obj. Any clues about what I'm doing incorrectly here?
In your example you're patching the B class, that's the object passed as the first argument. That class doesn't declare obj attribute on the class level and so AttributeError is raised. When you provide create=True it won't complain as that argument allows the obj attribute to be dynamically created when needed/accessed. But, that won't ever happen as the very first "access" of that attribute is its actual creation - no dynamic mocking ever happened.
A solution is to actually patch the method whose returned value would be assigned to the obj attribute, like:
#patch.object(ex.B, 'create_some_obj', FakeObjForB())

Python: how to get the *full* name of the function I am in

Is there a way so that the following code:
import traceback
def log(message):
print "%s: %s" %(traceback.extract_stack()[0:-1][-1][2], message)
def f1():
log("hello")
class cls(object):
def f1(self):
log("hi there")
f1()
mycls = cls()
mycls.f1()
displays:
f1: hello
cls.f1: hi there
instead of:
f1: hello
f1: hi there
?
I tried to use module 'inspect' but was not successful...
Julien
EDIT:
The point here is for 'log' function to be able to retrieve its caller
name on its own (using traceback, inspect, or any mean necessary).
I do not want to pass the class name, or anything else than 'message'
to the 'log' function.
So I finally came up this method:
#!/usr/bin/env python3
def log(message):
import inspect
import gc
code = inspect.currentframe().f_back.f_code
func = [obj for obj in gc.get_referrers(code) if inspect.isfunction(obj)][0]
print(func.__qualname__, message)
It needs python3 so that __qualname__ can be used.
The inspect module is very powerful, and can be used in a similar fashion to the way you use traceback to get the name of the function, and probably also the class name. But you can simply make use of the fact that you have the self instance variable, which knows of its own type/class:
import inspect
class cls(object):
def f1(self):
this_class_name = type(self).__name__
this_func_name = inspect.currentframe().f_code.co_name
print(this_class_name, this_func_name)
mycls = cls()
mycls.f1()

How to mock a property

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)

How do I mock a superclass's __init__ create an attribute containing a mock object for a unit test?

I am attempting to write a unit test for a class's __init__:
def __init__(self, buildNum, configFile = "configfile.txt"):
super(DevBuild, self).__init__(buildNum, configFile)
if configFile == "configfile.txt":
self.config.MakeDevBuild()
The config attribute is set by the super's __init__. I'm using mock, and I want the config attribute to be a mock object. However, I haven't been able to figure out how to actually make that happen. Here's the best I could come up with for the test:
def test_init(self):
with patch('DevBuild.super', create=True) as mock_super:
mock_MakeDevBuild = MagicMock()
mock_super.return_value.config.MakeDevBuild = mock_MakeDevBuild
# Test with manual configuration
self.testBuild = DevBuild("42", "devconfigfile.txt")
self.assertFalse(mock_MakeDevBuild.called)
# Test with automated configuration
self.testBuild = DevBuild("42")
mock_MakeDevBuild.assert_called_once_with()
However, this doesn't work--I get an error:
Error
Traceback (most recent call last):
File "/Users/khagler/Projects/BuildClass/BuildClass/test_devBuild.py", line 17, in test_init
self.testBuild = DevBuild("42")
File "/Users/khagler/Projects/BuildClass/BuildClass/DevBuild.py", line 39, in __init__
self.config.MakeDevBuild()
AttributeError: 'DevBuild' object has no attribute 'config'
Clearly I'm not setting the config attribute correctly, but I have no idea where exactly I should be setting it. Or for that matter, if what I want to do is even possible. Can anyone tell me what I need to do to make this work?
You can't mock __init__ by setting it directly - see _unsupported_magics in mock.py.
As for what you can do, you can mock __init__ by passing it to patch, like so:
mock_makeDevBuild = MagicMock()
def mock_init(self, buildNum, configFile):
self.config = MagicMock()
self.config.MakeDevBuild = mock_makeDevBuild
with patch('DevBuild.SuperDevBuild.__init__', new=mock_init):
DevBuild("42")
mock_makeDevBuild.assert_called_once_with()
where SuperDevBuild is a base class of DevBuild.
If you really want to mock super(), you can perhaps make a class and then bind __init__ to object manually, like
mock_makeDevBuild = MagicMock()
def get_mock_super(tp, obj):
class mock_super(object):
#staticmethod
def __init__(buildNum, configFile):
obj.config = MagicMock()
obj.config.MakeDevBuild = mock_makeDevBuild
return mock_super
with patch('DevBuild.super', create=True, new=get_mock_super):
DevBuild("42")
mock_makeDevBuild.assert_called_once_with()
which works, but is quite ugly..
I do it this way, mocking the inherited class init:
from unittest import mock
#mock.patch.object(HierarchicalConf, "__init__")
def test_super_init(self, mock_super_init):
# act
ConfigurationService('my_args')
# assert
mock_super_init.assert_called_once_with(args)
Given the class:
class ConfigurationService(HierarchicalConf):
def __init__(self, dag_name) -> None:
"""Wrapper of Hierarchical Conf."""
# ... my code
super().__init__(args)
And if you want to also mock the ConfigurationService init, you can do quite the same:
#mock.patch.object(ConfigurationService, "__init__")
def test_init(self, mock_init):
# act
ConfigurationService('my_args')
# assert
mock_init.assert_called_once_with('my_args')

Categories