Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to Mock class that inherits from another class (which creates/uses singleton object)

I have three classes and one unittest case: 1) A.py:

class A(object):
    def __new__(cls):
        """
         Overriding the __new__ method to make the A a singleTon class
         :return: cls.instance
         """
        if not hasattr(cls, 'instance') or not cls.instance:
             cls.instance = super(A, cls).__new__(cls)
             return cls.instance
        def execute():
             print("class A")
             return "Coming from class A"

2) B.py:

from A import A
class B(object):
    def __init__(self):
        """
         Initialize and declare any members/methods that can be passed over class that inherit this class
        """
        self.a = A()
        self.name = Name()
    def run(self):
        pass

class Name:
    def __init__(self):
        self.test_dict = {}

3) C.py

from B import B
class C(B):
    def __init__(self):
        super(C, self).__init__()
        self.result = None

    def run(self):
        self.result = self.A.execute()
        return self.result

4) test.py

import unittest
from unittest.mock import patch
from A import A
from B import B
from C import C

class TestMethods(unittest.TestCase):

    @patch('A.A')
    def test_basic(self, MockA):
        a = MockA()
        a.execute.return_value = "Testing code"

        c = C()
        result = c.run()
        self.assertEqual(result,"Testing code")

if __name__ == '__main__':
    unittest.main()

while executing test.py, I am getting following error:

ERROR: test_basic (main.TestMethods)

Traceback (most recent call last):

File "C:\Users\davinder\AppData\Local\Continuum\Anaconda3\lib\unittest\mock.py", line 1179, in patched
   return func(*args, **keywargs)
File "C:/Users/davinder/PythonCodes/Test Framework/test.py", line 14, in test_basic
   c = C()
File "C:\Users\davinder\PythonCodes\Test Framework\C.py", line 5, in __init__
   super(C, self).__init__()
 File "C:\Users\davinder\PythonCodes\Test Framework\B.py", line 8, in __init__
    self.a = A()
 File "C:\Users\davinder\PythonCodes\Test Framework\A.py", line 8, in __new__
    cls.instance = super(A, cls).__new__(cls)
 TypeError: super() argument 1 must be type, not MagicMock

 ----------------------------------------------------------------------
 Ran 1 tests in 0.018s

 FAILED (errors=1)

I want to test run() from class C.py by using different return value used from test.py(by patching). Thanks for help in advance

Edit: Even if I mock C in test.py, like this:

import unittest
from unittest.mock import patch
from A import A
from B import B
from C import C
from B import Name

class TestMethods(unittest.TestCase):

    @patch('C.C')
    @patch('A.A')
    def test_basic(self, MockA, MockC):
        a1 = MockA()
        a1.execute.return_value = "Testing code"

        c = MockC()
        c.a = a1
        c.name = Name()

        result = c.run()
        self.assertEqual(result,"Testing code")

 if __name__ == '__main__':
     unittest.main()

getting this error:

FAIL: test_basic (__main__.TestMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
    File "C:\Users\davinder\AppData\Local\Continuum\Anaconda3\lib\unittest\mock.py", line 1179, in patched
        return func(*args, **keywargs)
    File "C:/Users/davinder/PythonCodes/Test Framework/test.py", line 22, in test_basic
        self.assertEqual(result,"Testing code")
 AssertionError: <MagicMock name='C().run()' id='2464384921272'> != 'Testing code'

 ----------------------------------------------------------------------
 Ran 1 tests in 0.018s

 FAILED (failures=1)
like image 274
Davinder Pal Singh Chahal Avatar asked Sep 13 '25 19:09

Davinder Pal Singh Chahal


1 Answers

I had to change your import in B.py to be an import instead of a from. The error was because A was being looked up from the B module, so your patch would have to be @patch('B.A'). Here's a useful read anyway on that subject: http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch

Now, here's the changed code:

"""test.py"""
import unittest
from unittest.mock import patch
from c_module import C

class TestMethods(unittest.TestCase):
    @patch('b_module.A')
    def test_basic(self, mock_a):
        mock_a.return_value.execute.return_value = "Testing code"
        c = C()
        result = c.run()
        self.assertEqual(result,"Testing code")

if __name__ == '__main__':
    unittest.main()

"""c_module.py"""
from b_module import B
class C(B):
    def __init__(self):
        super(C, self).__init__()
        self.result = None
    def run(self):
        self.result = self.a.execute()
        return self.result

"""b_module.py"""
from a_module import A
class B(object):
    def __init__(self):
        """
         Initialize and declare any members/methods that can be passed over class that inherit this class
        """
        self.a = A()
        self.name = Name()
    def run(self):
        pass

class Name:
    def __init__(self):
        self.test_dict = {}

"""a_module.py"""
class A(object):
    def __new__(cls):
        """
         Overriding the __new__ method to make the A a singleTon class
         :return: cls.instance
         """
        if not hasattr(cls, 'instance') or not cls.instance:
             cls.instance = super(A, cls).__new__(cls)
             return cls.instance
        def execute():
             print("class A")
             return "Coming from class A"

I'd also encourage you to change your module naming. While working on this, it's hard to not conflate the modules with the classes. You can see above I've changed the module names to be snake-cased and left the classes as-is.

like image 109
wholevinski Avatar answered Sep 16 '25 07:09

wholevinski