Python Mocking Interview Questions: Patch Targets, Autospec, and AsyncMock
Quick Overview
Answer Python mocking interview questions with clear patch targets, autospec boundaries, AsyncMock await assertions, and diagnosis examples.
A Python test can patch a function successfully and still call the real dependency. Another test can pass while its mocked API bears no resemblance to production. A third can assert that an async function was called but never verify it was awaited. These are distinct mocking failures, and interviewers often learn more from how you diagnose them than from how quickly you type @patch.
The Python standard library documentation gives the central rule: patch the name where the system under test looks it up. Its autospeccing guidance and AsyncMock reference explain two further contracts: keep the mock aligned with the real interface, and assert coroutine await behavior explicitly. These are official Python behaviors. The small service and tests below are original practice code, not reported interview questions.
PracHub’s Validate Unit-Test Coverage and Identify Missing Scenarios is a useful companion: a mock is only as meaningful as the behavior your test actually asserts.

Where does Python look up the dependency?
Imagine gateway.py defines a fetch function. The application module imports the function into its own namespace:
from gateway import fetch
def load_user(user_id):
return fetch(user_id)
At import time, service.fetch becomes the name that load_user resolves. Patching gateway.fetch afterward changes the attribute in the original module, but it does not replace the binding already used by service.load_user. The right target for this example is service.fetch.
from unittest.mock import patch
from service import load_user
with patch("service.fetch", autospec=True) as fetch_mock:
fetch_mock.return_value = {"id": 7}
assert load_user(7) == {"id": 7}
fetch_mock.assert_called_once_with(7)
Do not memorize “always patch the consumer module” as a slogan without inspecting the code. If service.py instead says import gateway and calls gateway.fetch(user_id), the lookup is through service.gateway.fetch; patching the gateway.fetch object may affect that shared module object. If a dependency is captured in a default argument, closure, or instance field, ordinary attribute patching may not affect the captured reference at all. Draw the lookup path for the actual code before selecting the target string.
A strong interview answer explains why the wrong patch can appear to work: the test might never reach the call, a fixture might have replaced another dependency, or the real function might return the same value. Assert the dependency interaction and a meaningful observable result. If the test unexpectedly reaches a network or filesystem call, stop and inspect the binding rather than adding another broad patch.
What does autospec protect, and what does it not?
A plain Mock will happily accept a call with invented keyword arguments or allow many nonexistent attributes. That flexibility is useful during prototyping but can make a unit test green while production raises TypeError or AttributeError. autospec=True derives a mock’s signature and accessible attributes from the target being patched. A call like fetch_mock(7, imaginary=True) should then fail if the real fetch does not accept imaginary.
Autospec is a guardrail, not a replica of all runtime behavior. It does not prove that your returned fake data obeys the real service’s schema. It does not make a network call or validate authorization. Dynamic attributes created only in __init__ may require care when speccing classes or instances. The standard-library mock examples are useful for distinguishing what a mock checks from what an integration test should check.
Use spec_set when a test must also prevent setting attributes not present on the spec. Choose the level that catches mistakes without making ordinary test setup brittle. If you spec the wrong object, you will enforce the wrong API. The order is: identify the real lookup target, choose the interface contract, then configure only the values the test needs.
Why does AsyncMock need await assertions?
Calling an async function creates a coroutine-like object; the actual work is associated with awaiting it. AsyncMock records calls and awaits separately. A test that checks only assert_called_once_with can miss production code that creates but never awaits the coroutine. The standard-library reference provides assert_awaited_once_with for the stronger assertion.
Here is a small original service:
from gateway import fetch_user
async def load_user(user_id):
result = await fetch_user(user_id)
return result["name"]
A focused test can patch the name in async_service, set the awaited result, run the coroutine, and check the await:
import asyncio
from unittest.mock import patch
from async_service import load_user
with patch("async_service.fetch_user", autospec=True) as fetch_mock:
fetch_mock.return_value = {"name": "Ada"}
assert asyncio.run(load_user(7)) == "Ada"
fetch_mock.assert_awaited_once_with(7)
When the target is an async function, patch creates an AsyncMock by default in current supported Python versions, and autospeccing preserves the async-aware interface. In a real test suite, use the project’s async test runner conventions rather than nesting asyncio.run inside an already running event loop. Also await the call before asserting its result; configuring return_value is not a substitute for exercising the coroutine.

A diagnosis table for common false positives
| Symptom | Likely mistake | Discriminating check |
|---|---|---|
Real API call appears despite patch | Patching definition rather than lookup binding | Inspect import style and assert patched object was called. |
| Test accepts a nonexistent parameter | Unconstrained Mock | Add autospec against the actual callable and try the bad call. |
| Async test passes without completing work | Checked call but not await | Use assert_awaited_once_with; assert the returned value. |
| Mock count grows across tests | Shared mock or patch lifecycle leak | Scope patch to each test and reset only when sharing is intentional. |
| Test passes after production method rename | Mock fabricates attributes | Use a spec and run at least one integration-level contract check. |
The table is a way to choose the next experiment. It does not say every surprising test is a mock bug. A production function may also be wrong, or a fixture may have changed the control flow. Verify that the line under test runs before interpreting mock assertions.
How do decorators, fixtures, and cleanup affect reliability?
@patch decorators are concise, but a stack of them can obscure which injected argument belongs to which target. The order of injected mocks follows the nesting of decorators. In an interview, a short context manager often makes the dependency and scope clearer. If you use a test framework fixture, make its scope explicit. A module-scoped fake can retain call history or mutable return values between tests and create order-dependent failures.
Always ensure a started patcher is stopped, including when setup raises. A context manager or decorator handles that automatically. If you must call patcher.start() in setUp, register cleanup immediately. This is not just housekeeping: leaked patches can make later tests pass or fail for reasons unrelated to their own code.
Do not mock the thing whose behavior the test is supposed to prove. If your unit test replaces both the service method and its dependency, it may only assert your own configured return value. Mock at a boundary: network client, clock, queue, or file operation. Keep the business logic real. For a data transformation, use concrete input and expected output. For a retry loop, control the clock and dependency responses but assert the resulting attempts, errors, and final state.
What would a complete answer look like in an interview?
If asked “Why isn’t this patch taking effect?”, start by reading the import line and the call site. Say the fully qualified name that the code evaluates at runtime. Then place a narrow patch there and assert both the call and the externally visible result. If the call still reaches the real dependency, check whether the reference was captured earlier, whether the patch started after import-time work, or whether another path invokes the dependency.
If asked “Why does this test stay green after a refactor?”, check whether the mock accepts nonexistent attributes or arguments. Add autospec or spec_set where appropriate. Then add a small integration contract test for the real adapter if the fake data shape is important. A strict mock can still simulate an impossible server response; an integration test covers a different risk.
If asked “Did the async dependency run?”, distinguish call creation from awaiting. Show assert_awaited_once_with and inspect the final result or side effect. Also test the error path: if fetch_user raises, does load_user propagate, translate, or recover as specified? Configure side_effect on the async mock, await the service, and assert the promised behavior. Merely proving an exception was configured says nothing about how the service handled it.
Build a small test matrix before trusting the mock
For the synchronous service, test a successful ID, an exception from the gateway, and an invalid input if the service promises to reject it. For the async service, test the successful await, dependency failure, and cancellation or timeout only if the service contract addresses those cases. Each test should answer one behavior question. The mock’s call log is supporting evidence, not the whole outcome.
When a mock configuration becomes longer than the production interaction, consider a small fake implementation. A fake can model state and return realistic records while preserving a narrow interface. But keep it honest: if the real client’s behavior is relevant, validate the fake against the real contract. Mocking is about isolating a boundary, not hiding uncertainty about it.
Practice questions for the same testing skill
These verified PracHub pages exercise test completeness, dependency boundaries, and debugging. They are not promises that an interviewer will ask about unittest.mock specifically.
| Practice question | Focus |
|---|---|
| Validate Unit-Test Coverage and Identify Missing Scenarios | Identify what a green test failed to prove. |
| Implement a simple service with tests | Choose what stays real and what becomes a fake. |
| Debug and test a Python function in venv | Reproduce a failure in a controlled environment. |
| Debug and Explain Fixes | Explain the causal path to a fix. |
| Analyze and debug Python utilities | Read imports and state before patching. |
The best final explanation is compact: “The test patched the defining module, but the function read an already imported name in service. I patched service.fetch, autospecced the callable so incorrect arguments fail, and asserted the async version was awaited.” That explains the failure mode, the correction, and what the new test actually guarantees.
Sources and further reading
- Python documentation: where to patch
- Python documentation: autospeccing
- Python documentation: AsyncMock
- Python documentation: mock examples
One final check is mutation sensitivity: deliberately break the production call in a local branch or scratch copy and verify that the test fails for the expected reason. Change the argument from user_id to None, remove the await, or call an attribute that does not exist. If the same test stays green, its assertions are too far from the real behavior. Restore the original code afterward. This exercise exposes false confidence without requiring a large test suite, and it clarifies exactly what the mock is supposed to protect.
Comments (0)