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?
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 dofrom obj_b import *
(which is bad style by the way - only import the objects you need), you need to patch the object reference imported intoobj_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 ofmockB.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
test.py