How can I mock an object instantiated in the constructor? - python

I'm writing unit-tests with Pytest. I want to unit-test a class that has on its __init__ method an object that connects to a database:
data_model.py
from my_pkg.data_base_wrapper import DataBaseWrapper
class DataModel:
def __init__(self):
self.db = DataBaseWrapper()
self.db.update_data()
def foo(self):
data = self.db.get_some_data()
# make some processing and return a result
data_base_wrapper.py
class DataBaseWrapper:
def __init__(self):
# Init process of the wrapper
pass
def update_data(self):
# Connect to the database and perform some operations
pass
I've tried using monkeypatch on the DataBaseWrapper object of DataModel:
from my_pkg.data_model import DataModel
class MockDataBaseWrapper:
#staticmethod
def update_cache():
pass
#staticmethod
def get_table(table):
# Return some data for testing
pass
#pytest.fixture
def data_model(monkeypatch):
monkeypatch.setattr(DataModel, 'db', MockDataBaseWrapper)
data_model = DataModel()
return data_model
However, I get the following error:
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f10221669e8>
#pytest.fixture
def data_model(monkeypatch):
> monkeypatch.setattr(DataModel, 'db', MockDataBaseWrapper)
E AttributeError: <class 'dashboard.datamodel.DataModel'> has no attribute 'db'
I've read in the answers of similar questions, that I could try writing a sub-class of my DataBaseWrapper and change it on the DataModel class, but I'm in the same situation, as I cannot monkeypatch the attribute of the __init__ method. I can, though, if it is not in the __init__ method.
How can I write tests for this composition of classes? Suggestions on how to rewrite these classes or a different patter are also welcome.

The problem is that your MockDataBaseWrapper is totally unrelated to the DataBaseWrapper used in DataModel.
My proposal is to get rid of your MockDataBaseWrapper and:
If you want to keep your current structure, you can use patch to mock the DataBaseWrapper that is actually imported in data_model.py.
from mock import patch
from my_pkg.data_model import DataModel
def test_data_model():
with patch("my_pkg.data_model.DataBaseWrapper") as MockedDB:
mocked_db = MockedDB()
data_model = DataModel()
assert data_model.db is mocked_db
The patch context manager will replace the DataBaseWrapper class that gets imported in your data_model.py with a Mock instance and let you interact with that mock, which allows me here to verify that it got instantiated.
Note that it is very important to patch the class in the module where it is imported (and not in the model where it is defined, i.e we patch your_package.data_model.DataBaseWrapper and not your_package.data_base_wrapper.DataBaseWrapper)
If you don't mind changing your class, then the usual pattern is to inject the db parameter into the constructor of DataModel. Mocking it then becomes a piece of cake.
class DataModel:
def __init__(self, db):
self.db = db
self.db.update_data()
from mock import patch, Mock
from my_pkg.data_model import DataModel
def test_data_model():
mocked_db = Mock()
data_model = DataModel(mocked_db)
assert data_model.db is mocked_db

Related

How to mock a method inside a #singleton decorated class in python

The class itself calls in the init method a get_credentials method, which does I need to mock. Using unittest for mocking;
from unittest import TestCase, mock
from src.layer.utils.db import Db
#singleton
class Db:
def __init__(self):
self.get_credentials()
def get_credentials(self):
# stuff
pass
#Tried and failed:
#mock.patch('src.layer.utils.db.Db.get_credentials',get_creds_mock)
#mock.patch.object(Db, 'get_credentials', get_credentials_mock)
class DbMock:
def get_credentials(self):
pass
def get_credentials_mock(self):
pass
class TestDb(TestCase):
#mock.patch.object(Db, 'get_credentials', get_credentials_mock)
def test_init(self):
db = Db()
self.assertIsInstance(db, Db)
The code of the #singleton decorator class:
def singleton(cls):
instances = {}
def instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return instance
I need to mock the get_credentials function because it communicates with a server, which is not allowed in testing environment. So, I must return the json token itself.
Is there any feasible approach to mock that function?
You could use a solution like https://pypi.org/project/singleton-decorator/ which tackles exactly your problem.
if you cannot exchange the singleton decorator, because it is some kind of framework solution, with this particular solution you are stranded, because you cannot access the instances dictionary.
If you cannot for any reason use another package, but can modify your definition of the singleton wrapper, you could add this to your singleton code:
def singleton(cls):
instances = {}
def instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
instance.__wrapped__ = cls
return instance
and then you should be able to override as presented in the package:
#mock.patch('wherever.Db.__wrapped__.get_credentials')

Mocking class attributes and methods with Python and Pytest [duplicate]

How do you mock a readonly property with mock?
I tried:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
but the issue is that it then applies to all instances of the class... which breaks my tests.
Do you have any other idea? I don't want to mock the full object, only this specific property.
I think the better way is to mock the property as PropertyMock, rather than to mock the __get__ method directly.
It is stated in the documentation, search for unittest.mock.PropertyMock:
A mock intended to be used as a property, or other descriptor, on a class. PropertyMock provides __get__ and __set__ methods so you can specify a return value when it is fetched.
Here is how:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
def test(unittest.TestCase):
with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
Actually, the answer was (as usual) in the documentation, it's just that I was applying the patch to the instance instead of the class when I followed their example.
Here is how to do it:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
In the test suite:
def test():
# Make sure you patch on MyClass, not on a MyClass instance, otherwise
# you'll get an AttributeError, because mock is using settattr and
# last_transaction is a readonly property so there's no setter.
with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
myclass = MyClass()
print myclass.last_transaction
If the object whose property you want to override is a mock object, you don't have to use patch.
Instead, can create a PropertyMock and then override the property on the type of the mock. For example, to override mock_rows.pages property to return (mock_page, mock_page,):
mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
In case you are using pytest along with pytest-mock, you can simplify your code and also avoid using the context manger, i.e., the with statement as follows:
def test_name(mocker): # mocker is a fixture included in pytest-mock
mocked_property = mocker.patch(
'MyClass.property_to_be_mocked',
new_callable=mocker.PropertyMock,
return_value='any desired value'
)
o = MyClass()
print(o.property_to_be_mocked) # this will print: any desired value
mocked_property.assert_called_once_with()
Probably a matter of style but in case you prefer decorators in tests, #jamescastlefield's answer could be changed to something like this:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
class Test(unittest.TestCase):
#mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
def test(self, mock_last_transaction):
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
If you need your mocked #property to rely on the original __get__, you can create your custom MockProperty
class PropertyMock(mock.Mock):
def __get__(self, obj, obj_type=None):
return self(obj, obj_type)
Usage:
class A:
#property
def f(self):
return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
mock_foo.side_effect = new_get
print(A().f) # mocked result: 123
print(mock_foo.call_count) # 1
If you don't want to test whether or not the mocked property was accessed you can simply patch it with the expected return_value.
with mock.patch(MyClass, 'last_transaction', Transaction()):
...
I was directed to this question because I wanted to mock the Python version in a test. Not sure whether this is quite relevant to this question, but sys.version is obviously read-only (... though technically an "attribute" rather than a "property", I suppose).
So, after perusing this place and trying some stupidly complicated stuff I realised the answer was simplicity itself:
with mock.patch('sys.version', version_tried):
if version_tried == '2.5.2':
with pytest.raises(SystemExit):
import core.__main__
_, err = capsys.readouterr()
assert 'FATAL' in err and 'too old' in err
... might help someone.

Unable to patch class instantiated by the tested class using unittest

I am trying to patch a class that is instantiated by the class I am trying to test, but it doesn't work. I have read the various docs but still haven't found what I am doing wrong. Here is the code snippet:
In tests/Test.py:
from module.ClassToTest import ClassToTest
class Test(object):
#mock.patch('module.ClassToPatch.ClassToPatch', autospec = False)
def setUp(self, my_class_mock):
self.instance = my_class_mock.return_value
self.instance.my_method.return_value = "def"
self.class_to_test = ClassToTest()
def test(self):
val = self.class_to_test.instance.my_method() #Returns 'abc' instead of 'def'
self.assertEqual(val, 'def')
In module/ClassToPatch.py:
class ClassToPatch(object):
def __init__(self):
pass
def my_method(self):
return "abc"
In module/ClassToTest.py:
from module.ClassToPatch import ClassToPatch
class ClassToTest(object):
def __init__:
# Still instantiates the concrete class instead of the mock
self.instance = ClassToPatch()
I know in this case I could easily inject the dependency, but this is just an example. Also, we use a single class per file policy, with the file named like the class, hence the weird import naming.
As norbert mentions, the fix is to change the mock line from
#mock.patch('module.ClassToPatch.ClassToPatch', autospec = False)
to
#mock.patch('module.ClassToTest.ClassToPatch', autospec = False)
According to the docs:
The patch() decorator / context manager makes it easy to mock classes or objects in a module under test. The object you specify will be replaced with a mock (or other object) during the test and restored when the test ends.
You are testing the ClassToTest module, not the ClassToPatch module.

How to mock a readonly property with mock?

How do you mock a readonly property with mock?
I tried:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
but the issue is that it then applies to all instances of the class... which breaks my tests.
Do you have any other idea? I don't want to mock the full object, only this specific property.
I think the better way is to mock the property as PropertyMock, rather than to mock the __get__ method directly.
It is stated in the documentation, search for unittest.mock.PropertyMock:
A mock intended to be used as a property, or other descriptor, on a class. PropertyMock provides __get__ and __set__ methods so you can specify a return value when it is fetched.
Here is how:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
def test(unittest.TestCase):
with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
Actually, the answer was (as usual) in the documentation, it's just that I was applying the patch to the instance instead of the class when I followed their example.
Here is how to do it:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
In the test suite:
def test():
# Make sure you patch on MyClass, not on a MyClass instance, otherwise
# you'll get an AttributeError, because mock is using settattr and
# last_transaction is a readonly property so there's no setter.
with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
myclass = MyClass()
print myclass.last_transaction
If the object whose property you want to override is a mock object, you don't have to use patch.
Instead, can create a PropertyMock and then override the property on the type of the mock. For example, to override mock_rows.pages property to return (mock_page, mock_page,):
mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
In case you are using pytest along with pytest-mock, you can simplify your code and also avoid using the context manger, i.e., the with statement as follows:
def test_name(mocker): # mocker is a fixture included in pytest-mock
mocked_property = mocker.patch(
'MyClass.property_to_be_mocked',
new_callable=mocker.PropertyMock,
return_value='any desired value'
)
o = MyClass()
print(o.property_to_be_mocked) # this will print: any desired value
mocked_property.assert_called_once_with()
Probably a matter of style but in case you prefer decorators in tests, #jamescastlefield's answer could be changed to something like this:
class MyClass:
#property
def last_transaction(self):
# an expensive and complicated DB query here
pass
class Test(unittest.TestCase):
#mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
def test(self, mock_last_transaction):
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
If you need your mocked #property to rely on the original __get__, you can create your custom MockProperty
class PropertyMock(mock.Mock):
def __get__(self, obj, obj_type=None):
return self(obj, obj_type)
Usage:
class A:
#property
def f(self):
return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
mock_foo.side_effect = new_get
print(A().f) # mocked result: 123
print(mock_foo.call_count) # 1
If you don't want to test whether or not the mocked property was accessed you can simply patch it with the expected return_value.
with mock.patch(MyClass, 'last_transaction', Transaction()):
...
I was directed to this question because I wanted to mock the Python version in a test. Not sure whether this is quite relevant to this question, but sys.version is obviously read-only (... though technically an "attribute" rather than a "property", I suppose).
So, after perusing this place and trying some stupidly complicated stuff I realised the answer was simplicity itself:
with mock.patch('sys.version', version_tried):
if version_tried == '2.5.2':
with pytest.raises(SystemExit):
import core.__main__
_, err = capsys.readouterr()
assert 'FATAL' in err and 'too old' in err
... might help someone.

Mocking a class method that is used via an instance

I'm trying to patch a class method using mock as described in the documentation. The Mock object itself works fine, but its methods don't: For example, their attributes like call_count aren't updated, even though the method_calls attribute of the class Mock object is. More importantly, their return_value attribute is ignored:
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
u = User()
print(u.run())
>>>
mock
<MagicMock name='Lib().method()' id='39868624'>
What am I doing wrong here?
EDIT: Passing a class Mock via the constructor doesn't work either, so this is not really related to the patch function.
I have found my error: In order to configure the methods of my mock's instances, I have to use mock().method instead of mock.method.
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self):
l = Lib()
return l.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock().method.return_value = "mock"
u = User()
print(u.run())
from mock import *
class Lib:
"""In my actual program, a module that I import"""
def method(self):
return "real"
class User:
"""The class I want to test"""
def run(self, m):
return m.method()
with patch("__main__.Lib") as mock:
#mock.return_value = "bla" # This works
mock.method.return_value = "mock"
print User().run(mock)
I mock classmethods like this:
def raiser(*args, **kwargs):
raise forms.ValidationError('foo')
with mock.patch.object(mylib.Commands, 'my_class_method', classmethod(raiser)):
response=self.admin_client.get(url, data=dict(term='+1000'))

Categories