A mock can return exactly the value a test expects and still describe the wrong interface.
A bounded experiment on Python 3.14.7 used a synthetic Client with two methods: a regular send(recipient, *, urgent=False) and an async fetch(token, timeout=1.0). The test double was built with create_autospec(Client, instance=True, spec_set=True).
client = create_autospec(Client, instance=True, spec_set=True)
client.fetch.return_value = {"ok": True}
send_result = client.send("ops", urgent=True)
fetch_result = client.fetch("token-7", timeout=2.0)
The run produced a useful split. The regular method became a MagicMock and its result was not awaitable. The asynchronous method became an AsyncMock; calling it produced an awaitable, and awaiting it returned {"ok": true}. The same double also rejected client.send("ops", True) with TypeError: too many positional arguments, and rejected client.fetch() with TypeError: missing a required argument: 'token'.
The assertions had two different jobs. send.assert_called_once_with(...) checked the ordinary call. fetch.assert_awaited_once_with(...) checked that the asynchronous boundary was actually crossed. Both passed in the experiment.
For comparison, a loose MagicMock was given a fetch attribute whose return value was the same-looking dictionary. An ordinary call returned that dictionary, so a test that stopped there could look healthy. Awaiting the result failed with TypeError: 'dict' object can't be awaited. The loose mock had supplied data, but it had not supplied the contract that the caller used.
This was a constructed demonstration, not a product test or a benchmark. It used one synthetic class, Python 3.14.7 and unittest.mock; it does not prove that a real client is wired correctly or cover every autospec edge case. Its narrower result is practical: when a collaborator mixes synchronous and asynchronous methods, build the double from the interface and assert the await separately. A plausible return value is not evidence that the code under test waited at the right boundary.