Comparison with unittest.mock
Chainmock is built on top of Python's unittest.mock but provides a more intuitive and streamlined interface. Here are some key advantages of using Chainmock:
- Automatic teardown: Chainmock handles mock cleanup automatically at the end of each test.
- Lazy evaluation: Assertions are evaluated during teardown, not immediately when called.
- Method chaining: Multiple assertions can be chained together in a single line.
- Less boilerplate: More concise syntax for common mocking patterns.
Chainmock also provides some additional features not available in unittest.mock, such as:
- Partial argument matching.
- More granular call count assertions (e.g.,
call_count_at_least,call_count_at_most). - Spying without mocking which is not directly supported by
unittest.mock.
Let's look at some practical examples comparing unittest.mock and Chainmock approaches.
Basic mocking and assertions
Mocking a method return value
With unittest:
teapot = Teapot()
with mock.patch.object(teapot, 'add_tea') as mocked:
mocked.return_value = 'mocked'
assert teapot.add_tea('green') == 'mocked'
mocked.assert_called_once_with('green')
With Chainmock:
teapot = Teapot()
mocker(teapot).mock('add_tea').return_value('mocked').called_once_with('green')
assert teapot.add_tea('green') == 'mocked'
Side effects and exceptions
Raising exceptions
With unittest:
teapot = Teapot()
with mock.patch.object(teapot, 'brew') as mocked:
mocked.side_effect = ValueError("No tea!")
try:
teapot.brew()
except ValueError as e:
assert str(e) == "No tea!"
mocked.assert_called_once()
With Chainmock:
teapot = Teapot()
mocker(teapot).mock('brew').side_effect(ValueError("No tea!")).called_once()
try:
teapot.brew()
except ValueError as e:
assert str(e) == "No tea!"
Sequential return values
With unittest:
teapot = Teapot()
with mock.patch.object(teapot, 'fill') as mock_fill:
mock_fill.side_effect = ['full', 'overfull', 'spilling']
assert teapot.fill() == 'full'
assert teapot.fill() == 'overfull'
assert teapot.fill() == 'spilling'
assert mock_fill.call_count == 3
With Chainmock:
teapot = Teapot()
mocker(teapot).mock("fill").side_effect(
["full", "overfull", "spilling"]
).call_count(3)
assert teapot.fill() == 'full'
assert teapot.fill() == 'overfull'
assert teapot.fill() == 'spilling'
Stubs
Stubbing attributes
With unittest:
stub = mock.Mock()
stub.attribute = 'value'
stub.other_attribute = 'other_value'
assert stub.attribute == 'value'
assert stub.other_attribute == 'other_value'
With Chainmock:
stub = mocker(attribute='value', other_attribute='other_value')
assert stub.attribute == 'value'
assert stub.other_attribute == 'other_value'
Stubbing methods
With unittest:
stub = mock.Mock()
stub.method.return_value = 'mocked'
assert stub.method() == 'mocked'
With Chainmock:
stub = mocker(method=lambda: 'mocked')
assert stub.method() == 'mocked'