How to mock properly classes and methods from imported modules

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

Answers (1)

Stop harming Monica
Stop harming Monica

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.

  • You have to patch Class_1, yet it doesn't even appear in the test.
  • It is not obvious why 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.
  • The behavior of the method depends (hopefully) on 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

Related Questions