I need to check the sequence of calling of some methods of a class.
I suppose that my production class A
is stored in the following file class_a.py:
class A:
__lock = None
def __init__(self, lock):
self.__lock = lock
def method_1(self):
self.__lock.acquire()
self.__atomic_method_1()
self.__lock.release()
def __atomic_method_1(self):
pass
I need to write a unit test that checks the sequence of calling of the methods of the class A
when it is invoked the method_1()
.
The check must verify that method_1()
calls:
- as first method:
self.__lock.acquire()
- after that it calls
self.__atomic_method_1()
- and that the last method called is
self.__lock.release()
A useful hint but not enough
Your SUT has two references :
- one named
__locked
which points to itsLock
instance, which itself has (for our concerns) 2 references :acquire
andrelease
- the other named
__atomic_method_1
which points to itsA.__atomic_method_1
method
What we want is to observe the calls made to __lock.acquire
, __lock.release
, __atomic_method_1
and their relative order.
The simpler way to do that I could think of is to replace each of these three by "spy functions", which records they were being called (simply by appending in a list) then forward the call to the actual function.
But then we need these functions to be called, so we will have to mock things. Because they are not "importable", we can't
The calls to __lock.acquire
and __lock.release
are sort of diverted (thanks to mocking) through our spy, while any other still gets through ordinarily.
(We could have done without creating a Mock for __aotmic_method_1
, and mock.patch.object
with the spy function)
CodePudding user response:
The solution of @Lenormju is obviously rich of concepts about Mocking and, in my opinion, is preferable than this. In fact I have accepted it.
However I propose an other answer that can be used to solve some testing problem and my specific test case.
The test method that I have written is based on the following ideas:
- create a Mock instance by the method
mock.create_autospec()
:
mock_a = mock.create_autospec(A)
- invoke the instance method
method_1()
by the classA
and passing it the Mock instance:
A.method_1(mock_a)
The complete test file is the following:
import unittest
from unittest import mock
from class_a import A
class MyTestCase(unittest.TestCase):
def test_call_order(self):
mock_a = mock.create_autospec(A)
expected = [mock.call._A__lock.acquire(),
mock.call._A__atomic_method_1(),
mock.call._A__lock.release()]
A.method_1(mock_a)
self.assertEqual(expected, mock_a.mock_calls)
if __name__ == '__main__':
unittest.main()
The test doesn't use an instance of the class under test
One of the problem of this test is that it doesn't create an instance of class A
and so it invokes method_1()
in a different way respect of the production code.
On the other hand this is a specific test that I have to use to check the static structure of the method_1()
code so, in my opinion and only in this specific case, the trick could be acceptable.