Most of my unit testing experience is with Java and now I'm turning to Python. I need to test whether a method (from object B) gets called inside another method (in object A).
In Java the test method would have to pass a mock or spy version of B to A's constructor to be used when the B method is invoked. Do I need to do the same in Python? Or is there a simpler way? (I raise the possibility of the latter, because it seems, from what little I know, that Python is relatively relaxed about enforcing isolation between different components.)
Below is how I do this the "Java way." There are two Python files under test (for objects A and B) and a test program. Notice that object A's constructor had to be modified to accommodate testing.
obj_a.py
from obj_b import *
class ObjA:
def __init__(self, *args):
if len(args) > 0:
self.objb = args[0] # for testing
return
self.objb = ObjB()
def methodCallsB(self, x, y):
return self.objb.add(x, y)
obj_b.py
class ObjB:
def add(self, x, y):
return x y
test.py
import unittest
from unittest.mock import patch, Mock
from obj_a import *
from obj_b import *
class TTest(unittest.TestCase):
@patch("obj_b.ObjB")
def test_shouldCallBThroughA(self, mockB):
# configure mock
mockB.add = Mock(return_value=137)
obja = ObjA(mockB)
# invoke test method
res = obja.methodCallsB(4, 7)
print("result: " str(res))
# assess results
self.assertEqual(137, res)
mockB.add.assert_called_once()
args = mockB.add.call_args[0] # Python 3.7
print("args: " str(args))
self.assertEqual((4, 7), args)
if __name__ =='__main__':
unittest.main()
Again, is there a simpler way to test that ObjB::add is called from ObjA?
CodePudding user response:
Apart from the possible problems with the design, mentioned in the comment by @Alex, there is a couple of errors in using the mock.
First, you are mocking the wrong object. As in object_a
you do from obj_b import *
(which is bad style by the way - only import the objects you need), you need to patch the object reference imported into obj_b
, e.g. obj_a.ObjB
(see where to patch).
Second, you have to mock the method call on the instance instead of the class, e.g. mock mockB.return_value.add
instead of mockB.add
.
Your tests actually only work because you are not testing your real function, only your mock. If you do the patching correctly, there is no need to add that test-specific code in __init__
.
So, put together, something like this should work:
obj_a.py
class ObjA:
def __init__(self):
self.objb = ObjB()
...
test.py
class TTest(unittest.TestCase):
@patch("obj_a.ObjB")
def test_shouldCallBThroughA(self, mockB):
# for convenience, store the mocked method
mocked_add = mockB.return_value.add
mocked_add.return_value = 137
obja = ObjA()
res = obja.methodCallsB(4, 7)
self.assertEqual(137, res)
mocked_add.assert_called_once()
args = mocked_add.call_args[0]
self.assertEqual((4, 7), args)