Reputation: 13
I'm trying to create some unitary tests for a method that needs to instantiate some objects using classes from external modules that I don't want to use, as they need arguments that I can't pass at object initialization.
For example, imagine that the code I want to test has the following structure:
from module_one import Class_1
from module_two import Class_2
class MyMainClass()
def method_to_be_tested(self, stuff):
var_one = Class_1(stuff.data)
self.var_two = Class_2(var_one.data)
print("The var is %s" % self.var_two.attribute)
stuff
is a complex argument, with several attributes that I can't mock.
This is what I have tried in my test method, using patch
from unittest.mock
(but it didn't work):
@patch('module_two.Class_2')
@patch('module_one.Class_1')
def test_method(self, mocked_class1, mocked_class2):
stuff = mock()
mocked_class1.return_value = 'some_stuff_that_i_dont_want'
mock_class2 = mock()
mock_class2.attribute = 'what_i_want_to_get'
mocked_class2 = mock_class2
mymainclass.method_to_be_tested(stuff)
assertEqual('what_i_want_to_get', mymainclass.var_two.attribute)
It seems that the patch or something isn't working, as it throws an error telling me that str object has no attribute data, referring to var_one
when var_one.data
is used as an argument for Class2
.
What I want is to pass any argument to Class2
and get always what I defined in mock_class2
.
Edit: mock()
is imported from mockito
module, but maybe I don't need this to achieve what I need.
Upvotes: 0
Views: 1723
Reputation: 12590
You can certainly do that without mockito. Assuming that MyMainClass
is defined in module mainmodule
:
import unittest.mock as mock
class TestMyMainClass(unittest.TestCase):
@mock.patch('mainmodule.Class_2')
@mock.patch('mainmodule.Class_1')
def test_method(self, mocked_class1, mocked_class2):
stuff = mock.Mock()
mocked_class2_instance = mock.Mock()
mocked_class2_instance.attribute = 'what_i_want_to_get'
mocked_class2.return_value = mocked_class2_instance
mymainclass = MyMainClass()
mymainclass.method_to_be_tested(stuff)
self.assertEqual('what_i_want_to_get', mymainclass.var_two.attribute)
if __name__ == '__main__':
unittest.main()
Now if you think this test sucks, I have to agree.
Class_1
, yet it doesn't even appear in the test.Class_2
is relevant at all unless you are familiar with the internal implementation details of the method under test. Actually you are testing implementation details instead of (or in addition to) observable behavior, which is usually bad.stuff.data
but the test ignores that and will happily pass whatever it is. An yet you need a stuff
object with a data
attribute even if you don't care about what it is.The main problem here is that by instantiating Class_1
and Class_2
inside the method you are tightly coupling the three pieces of code but now you want to test them in isolation. That's going to be hard and will probably lead to fragile and hard to read tests.
If you really want your code to be like that I suggest you test the method without mocking neither Class_1
nor Class_2
. Now if you say you don't want to do that because stuff
(or better, stuff.data
; why are you passing the whole think when you only need the data?) is hard to mock or instantiate, I would guess that there is a problem with your design, but that is as far as I can get from your contrived example.
Upvotes: 1