Hi,
I'd like to ask for ideas of best practices in case of developing and testing
a wrapper for a Python legacy module.
Especially, about applying the single assert / single mock rule
which I have been learning about from numerous books/videos/blogs.
I have Python xdep module which exposes API in form of bunch of free functions.
I'm creating new Python module foo which uses xdep (the idea is to provide
simplified, Pythonic interface, but that is not directly relevant to
my question here).
I'm trying to keep the code as simple & generic as possible, so my question is
not about Python but unit testing and mocking.
# xdep.py
def open():
...
def get_item(item_id):
...
def close():
...
# foo.py
import xdep
def exists(item_id):
xdep.open()
item = xdep.get_item(item_id)
xdep.close()
if item:
return item.id == item_id
else:
return false
Here is my module to unit test behaviuour of the foo:
- I create and plug a stub for the legacy module xdep
- I mock functions of xdep
# test_foo.py
import unittest
import unittest.mock as mock
class ItemTest(unittest.TestCase):
def setUp(self):
# create stub for external dependency of foo
self.xdep_stub = mock.Mock()
patcher = mock.patch.dict('sys.modules', {'xdep': self.xdep_stub})
self.addCleanup(patcher.stop)
patcher.start()
# import foo does import xdep, as patched above
import foo
self.foo = foo
def test_exists_ExistingItemId_ReturnTrue(self):
foo = self.foo
# Arrange
### Data
item_id = 1
### Mock
foo.xdep.get_item.return_value = 1
# Act
item_exists = foo.item.exists(item_id)
# Assert
self.assertTrue(item_exists)
I have a single assertion and I use single mock which mocks xdep.get_item
function, so it seems a valid yet clear unit test.
Now, having the single assert/single mock rule in mind, I have some questions,
about the test_exists_ExistingItemId_ReturnTrue in particular:
1) How asserting on foo.item.exists return value is different from
asserting how xdep.get_item mock was called?
I could have replaced
self.assertTrue(item_exists)
with
foo.xdep.get_item.assert_called_once_with(1)
2) What about having both assertions, would that break the single
assertion rule?
self.assertTrue(item_exists)
foo.xdep.get_item.assert_called_once_with(1)
I'm not concern about breaking the rule as technical convention, but as:
am I testing multiple things here?
3) AFAIU, it's natural that unit tests are coupled with implementation of
method/behaviour they are testing, foo.exists function in my case.
So, given that foo.exists function calls three functions of the legacy
xdep module
xdep.open
xdep.get_item
xdep.close
should I mock all the three function and verify how they were called?
Would that still belong to scope of test_exists_ExistingItemId_ReturnTrue test
or I better create a new dedicated test case(s), something like
test_exists_IfCalled_xdepOpenCalled
test_exists_IfCalled_xdepCloseCalled
test_exists_IfCalled_xdepGetItemCalled
and do assert_called_once_with() appropriately.
Above, I count each mock for each xdep function as a separate mock,
so "single mock per test" rule seems to suggests I should verify they are called
in separate tests, doesn't it?
Also, some of other xdep functions need to be called with multiple parameters,
and as setting up and verifying their mocks may be quite elaborate,my
gut feeling
tells me that single unit test per "legacy API call expectation" as listed above
is a good approach for readability and maintainability of tests.
Am I right?
4) What other, if any, approach to single assert/single mock rules
would you take for testing similar case as above?
Best regards,
--
Mateusz ?oskot,