Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock superclass __init__ method or superclass as a whole for testing

I want to test a Python class I wrote, which is like the following:

from external_library import GenericClass

class SpecificClass(GenericClass):

  def __init__(self, a, b, c):
    super(SpecificClass, self).__init__(a, b)
    self.c = c

  def specific_method(self, d):
    self.initialize()
    return d + self.c + self.b + self.a.fetch()

GenericClass is defined by an external library (external_library):

class GenericClass(object):

  def __init__(self, a, b):
    self.a = a
    self.b = b + "append"

  def initialize(self):
    # it might be an expensive operation
    self.a.initialize()

  def specific_method(self, d):
    raise NotImplementedError

How should I test specific_method? Other than GenericClass.initialize, should I mock out GenericClass as a whole, GenericClass.__init__, super or GenericClass.a and GenericClass.b? Or my approach to the problem is totally wrong?

Please use mock as mocking library and pytest as test framework. The solution is required to be Python2.6+ compatible.

Thanks in advance.

like image 651
poros Avatar asked Jan 12 '15 17:01

poros


1 Answers

Is little bit hard to guess the best approach from a synthetic example. When is possible use just the needed mocks and the most of possible real object is the best. But it is a trade off game and when create real object is hard and dependencies are deep and twisted mocks can be very powerful tool. Moreover mocks give to you lot of sense points where your test can play. There is a price to pay: sometimes by use mock you can hide bugs that can be raised just by integration tests.

IMHO for your case the best is just mock a and set fetch's return value. You can also test a.initialize() call

If your target is just mock a.initialize() (and off course a.fetch() that I guess have no sense without initialize()); the best thing is just patch a object.

Anyway last test is about patching the superclass. In your case that is little tricky you must both patch __init__ and inject a, b attribute. If you are worry about what GenericClass do in init you must patch it directly __init__ method: patch the class is useless because its reference is already in SpecificClass definition (it is resolved by super and not by GenericClass.__init__(...)). Maybe in the real code you should fight against read only property for a and b: again if you can use real object and just patch less method/object as possible is the best choice. I love mock framework but some times use it is not the best deal.

One more thing: I used patch.multiple just to have a more compact example, but patch the method by two patch.object do the same work.

import unittest
from mock import Mock, patch, DEFAULT, PropertyMock
from specific import SpecificClass


class A():
    def initialize(self):
        raise Exception("Too slow for tests!!!!")

    def fetch(self):
        return "INVALID"


class MyTestCase(unittest.TestCase):
    def test_specific_method(self):
        a = A()
        with patch.multiple(a, initialize=DEFAULT, fetch=DEFAULT) as mocks:
            mock_initialize, mock_fetch = mocks["initialize"], mocks["fetch"]
            mock_fetch.return_value = "a"
            sc = SpecificClass(a, "b", "c")
            self.assertEqual("dcbappenda", sc.specific_method("d"))
            mock_initialize.assert_called_with()

    def test_sanity_check(self):
        a = A()
        sc = SpecificClass(a, "b", "c")
        self.assertRaises(Exception, sc.specific_method, "d")

    #You can patch it in your module if it is imported by from .. as ..
    @patch("specific.GenericClass.__init__", autospec=True, return_value=None)
    def test_super_class(self, mock_init):
        sc = SpecificClass("a", "b", "c")
        mock_init.assert_called_with(sc, "a", "b")
        #Inject a and b
        sc.a = Mock()
        sc.b = "b"
        #configure a
        sc.a.fetch.return_value = "a"
        self.assertEqual("dcba", sc.specific_method("d"))
        sc.a.initialize.assert_called_with()
like image 181
Michele d'Amico Avatar answered Nov 01 '22 09:11

Michele d'Amico