How to mock class attributes with supplied user value? - python

I would like to mock my class attribute under test.
The class is as follows:
import path.to.some.other.class.OtherClass
class ClassToTest:
def __init__(self, some_path):
self._attribute_to_test = OtherClass(some_other_path)
#rest of the class details
..
Unit Test that I tried to write is as follows (but does not work as expected):
with patch("path.to.some.other.class.OtherClass") as _mock:
instance = _mock.return_value
ClassToTest._attribute_to_test = instance
Now when I try to instantiate the ClassToTest the attribute attribute_to_test does not mock.

Related

How can I mock an object instantiated in the constructor?

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

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.

unittest.TestCase gets `TypeError: Error when calling the metaclas bases __init__() takes exactly 2 arguments (4 given)`

I am trying to add attributes to a unittest TestCase, but I keep getting the following error.
TypeError: Error when calling the metaclas bases __init__() takes exactly 2 arguments (4 given) I am running the tests with nose.
But I am passing only one argument. self makes it two. So I have no idea where the 3rd and 4th arguments are coming from. Is there something in unittest I should be aware of? or nose?
Base_Test.py
import unittest
from logUtils import logger
class Base_Test(unittest.TestCase):
def __init__(self, p4rev):
self.p4rev = p4rev
def setUp(self):
logger.critical(p4rev)
triggerSomething(p4rev)
My_test.py
from Base_Test import Base_Test
rev = '12345'
class check_error_test(Base_Test(rev)):
dosomething()
When inheriting from a sub class from a supper class the python syntax comes as,
class SubClassName(SupperClassName):
#Sub class body
But in your coding,
class check_error_test(Base_Test(rev)):
dosomething()
you are parsing an instance of Base_Test class, but not the class. Base_Test(rev) will create an instance of Base_Test class.
You can modify your code as bellow.
class check_error_test(Base_Test):
def __init__(p4rev):
super(check_error_test).__init__(p4rev)
Base_Test is also a class that inherits from unittest.TestCase, so you need to add to Base_Test.py:
class Base_Test(unittest.TestCase):
def __init__(self, methodName='runTest', p4rev=None):
super(Base_Test, self).__init__(methodName)
self.p4rev = p4rev
def runTest(self):
pass
the runTest() was added there in the BaseTest class just to avoid the ValueError raise by the unittest.TestCase class. unittest.TestCase raises this error if there is no method to be found by the name passed to methodName parameter.
Bellow is the code segment which raises the ValueError which is found in the unittest.TestCase.init()
def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test
method when executed. Raises a ValueError if the instance does
not have a method with the specified name.
"""
self._testMethodName = methodName
self._resultForDoCleanups = None
try:
testMethod = getattr(self, methodName)
except AttributeError:
raise ValueError("no such test method in %s: %s" %
(self.__class__, methodName))
Try this out:
from Base_Test import Base_Test
rev = '12345'
class check_error_test(Base_Test):
def __init__(self):
super(check_error_test, self).__init__(rev)
dosomething()
It wasn't working because you were instantiating Base_Test while setting up the inheritance for check_error_test. There's a good section in the Python documentation on super() as well, if you'd like to know more about how that works.
In addition, I would also recommend capitalizing the class name to CheckErrorTest and changing Base_Test to BaseTest, as per the pep8 guidelines.

Unit tests organisation - how not to run base class tests?

I have tests like this:
import unittest
class TestBase(unittest.TestCase):
def setUp(self):
self.decorator = None
def testA(self):
data = someGeneratorA().generate()
self.assertTrue(self.decorator.someAction(data))
def testB(self):
data = someGeneratorB().generate()
self.assertTrue(self.decorator.someAction(data))
def testC(self):
data = someGeneratorC().generate()
self.assertTrue(self.decorator.someAction(data))
class TestCaseA(TestBase):
def setUp(self):
self.decorator = SomeDecoratorA
class TestCaseB(TestBase):
def setUp(self):
self.decorator = SomeDecoratorB
As you see, TestCaseA and TestCaseB is very similar, so I made TestBase class which implement body of testA, testB and testC method.
TestCaseA different from TestCaseB only decorator parameter.
So, I would like to ask, is any better way to organize my tests? And I have problem beacuse TestBase class - it's test's - shouldn't be runned ever (self.decorator is None so it will rase an exception)
Anything that inherits from unittest.TestCase is seen as a set of tests.
You could instead have your base class not inherit from TestCase, moving that base class to your concrete test classes instead:
class TestBase(object):
# base tests to be reused
class TestCaseA(TestBase, unittest.TestCase):
# Concrete tests, reusing tests defined on TestBase
class TestCaseB(TestBase, unittest.TestCase):
# Concrete tests, reusing tests defined on TestBase

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