Spies¶
Unlike mocks, spies wrap around objects and usually forward all
method calls to the original object. Despite its name, mock is not only suitable for mocks: the wraps
argument of the Mock class can also be
used to create spies. Calls are then forwarded to the wrapped object and return
its actual results, for example:
>>> def test_workinngday():
... spy = Mock(wraps=is_workingday, return_value=monday)
... assert is_workingday()
...
However, wraps only provides the mock’s default behaviour; other
configurations take precedence. If you set return_value, the wrapped object
is not called at all.
If, on the other hand, you set unittest.mock.Mock.side_effect, this
is executed instead of the wrapped object, unless it returns
unittest.mock.DEFAULT; in which case, the call is forwarded.
For example, when implementing the delete command, we can check whether the Tasks CLI handles error conditions correctly:
@app.command()
def delete(task_id: int):
"""Remove task in db with given id."""
with tasks_db() as db:
try:
db.delete_task(task_id)
except tasks.InvalidTaskId:
print(f"Error: Invalid task id {task_id}")
To test how the CLI handles an error condition, we can simulate
delete_task() throwing an exception by assigning the Exception to
the mock object’s side_effect attribute, something like this:
def test_delete_invalid(mock_tasksdb, tasks_cli):
mock_tasksdb.delete_task.side_effect = tasks.api.InvalidTaskId
out = tasks_cli("delete 42")
assert "Error: Invalid task id 42" in out