Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid executing __init__ of mocked class

I have a class with an expensive __init__ function. I don't want this function called from tests.

For the purpose of this example, I made a class that raises an Exception in __init__:

class ClassWithComplexInit(object):

    def __init__(self):
        raise Exception("COMPLEX!")

    def get_value(self):
        return 'My value'

I have a second class that constructs an instance of ClassWithComplexInit and uses it's function.

class SystemUnderTest(object):

    def my_func(self):
        foo = ClassWithComplexInit()
        return foo.get_value()

I am trying to write some unit tests around SystemUnderTest#my_func(). The problem I am having is no matter how I try to mock ClassWithComplexInit, the __init__ function always gets executed and the exception is raised.

class TestCaseWithoutSetUp(unittest.TestCase):

    @mock.patch('mypackage.ClassWithComplexInit.get_value', return_value='test value')
    def test_with_patched_function(self, mockFunction):
        sut = SystemUnderTest()
        result = sut.my_func()  # fails, executes ClassWithComplexInit.__init__()
        self.assertEqual('test value', result)

    @mock.patch('mypackage.ClassWithComplexInit')
    def test_with_patched_class(self, mockClass):
        mockClass.get_value.return_value = 'test value'
        sut = SystemUnderTest()
        result = sut.my_func()  # seems to not execute ClassWithComplexInit.__init__()
        self.assertEqual('test value', result)  # still fails
        # AssertionError: 'test value' != <MagicMock name='ClassWithComplexInit().get_value()' id='4436402576'>

The second approach above is one that I got from this similar Q&A but it didn't work either. It seemed to not run the __init__ function but my assertion fails because the result ends up being a mock instance as opposed to my value.

I also tried to configure a patch instance in the setUp function, using the start and stop functions as the docs suggest.

class TestCaseWithSetUp(unittest.TestCase):

    def setUp(self):
        self.mockClass = mock.MagicMock()
        self.mockClass.get_value.return_value = 'test value'
        patcher = mock.patch('mypackage.ClassWithComplexInit', self.mockClass)
        patcher.start()
        self.addCleanup(patcher.stop)

    def test_my_func(self):
        sut = SystemUnderTest()
        result = sut.my_func()  # seems to not execute ClassWithComplexInit.__init__()
        self.assertEqual('test value', result)  # still fails
        # AssertionError: 'test value' != <MagicMock name='mock().get_value()' id='4554658128'>

This also seems to avoid my __init__ function but the value I set for get_value.return_value isn't being respected and get_value() is still returning a MagicMock instance.

How can I mock a class with an complicated __init__ which is instantiated by my code under test? Ideally, I would like a solution which works well for many unit tests within a TestCase class (e.g. Not needing to patch every test).

I am using Python version 2.7.6.

like image 621
Jesse Webb Avatar asked Oct 20 '22 23:10

Jesse Webb


1 Answers

First, you need to use the same name you are patching to create foo, that is,

class SystemUnderTest(object):

    def my_func(self):
        foo = mypackage.ClassWithComplexInit()
        return foo.get_value()

Second, you need to configure the correct mock object. You are configuring ClassWithComplexInit.get_value, the unbound method, but you need to configure ClassWithComplexInit.return_value.get_value, which is the Mock object that will be actually be called with foo.get_value().

@mock.patch('mypackage.ClassWithComplexInit')
def test_with_patched_class(self, mockClass):
    mockClass.return_value.get_value.return_value = 'test value'
    sut = SystemUnderTest()
    result = sut.my_func()  # seems to not execute ClassWithComplexInit.__init__()
    self.assertEqual('test value', result)  # still fails
like image 174
chepner Avatar answered Oct 22 '22 14:10

chepner