0
0
PyTesttesting~15 mins

Mock return values and side effects in PyTest - Deep Dive

Choose your learning style9 modes available
Overview - Mock return values and side effects
What is it?
Mock return values and side effects are ways to control what a fake function or object does during a test. Instead of running real code, mocks let you decide what they return or what extra actions they perform. This helps test parts of your program in isolation, without relying on real external systems or complex logic. It makes tests faster, simpler, and more reliable.
Why it matters
Without mocks controlling return values and side effects, tests would depend on real systems like databases or web services. This makes tests slow, flaky, and hard to run anywhere. Mocking return values and side effects lets you simulate different scenarios easily, catching bugs early and saving time. It also helps you test error handling and edge cases that are hard to reproduce with real components.
Where it fits
Before learning this, you should understand basic pytest usage and what mocking means. After this, you can learn about advanced mocking techniques, patching, and integration testing. This topic fits in the middle of the testing journey, bridging simple unit tests and complex test setups.
Mental Model
Core Idea
Mock return values and side effects let you control what a fake function returns or does, so you can test your code without running real dependencies.
Think of it like...
It's like pretending to be a waiter in a restaurant who always brings the dish you want, no matter what you order, so you can test how you eat without waiting for the kitchen.
┌───────────────┐       ┌───────────────┐
│   Your Code   │──────▶│   Mock Object │
└───────────────┘       └───────────────┘
          │                      │
          │                      │
          │          ┌───────────┴───────────┐
          │          │ Return Value or Side   │
          │          │ Effect (custom action) │
          │          └───────────────────────┘
Build-Up - 7 Steps
1
FoundationWhat is a Mock Object
🤔
Concept: Introduce the idea of a mock as a fake object used in tests to replace real ones.
A mock object is a stand-in for a real object or function in your code. Instead of calling the real thing, your test calls the mock. This lets you control what happens without running real code. For example, if your code calls a function that fetches data from the internet, you can replace it with a mock that returns fixed data instantly.
Result
You can run tests without depending on real external systems.
Understanding mocks is the first step to isolating your tests and making them reliable and fast.
2
FoundationSetting a Mock's Return Value
🤔
Concept: Learn how to tell a mock what value to return when called.
In pytest, you can create a mock and set its return_value attribute. For example: from unittest.mock import Mock mock_func = Mock() mock_func.return_value = 42 Now, calling mock_func() will return 42 every time.
Result
mock_func() returns 42 instead of running real code.
Setting return values lets you simulate expected outputs easily.
3
IntermediateUsing Side Effects for Dynamic Behavior
🤔Before reading on: do you think side effects only change return values, or can they do more? Commit to your answer.
Concept: Side effects let mocks do more than just return values; they can run functions, raise errors, or change state.
Instead of a fixed return value, you can assign a function or an exception to side_effect. For example: def side_effect_func(): return 'dynamic result' mock_func.side_effect = side_effect_func Now, mock_func() calls side_effect_func and returns its result. You can also make side_effect raise exceptions to test error handling.
Result
mock_func() returns 'dynamic result' or raises an error if side_effect is an exception.
Side effects let you simulate complex or changing behaviors in your mocks.
4
IntermediateSide Effects with Multiple Calls
🤔Before reading on: do you think side_effect can handle different results for each call, or does it always do the same thing? Commit to your answer.
Concept: Mocks can return different values or raise different exceptions on each call using side_effect as a list.
You can assign a list to side_effect, and each call to the mock returns the next item: mock_func.side_effect = [1, 2, 3] Calling mock_func() three times returns 1, then 2, then 3. If the list ends, further calls raise StopIteration. This helps test loops or retries.
Result
mock_func() returns 1, then 2, then 3 on consecutive calls.
Using lists as side effects helps simulate sequences of events or responses.
5
IntermediateRaising Exceptions with Side Effects
🤔
Concept: Side effects can simulate errors by raising exceptions when the mock is called.
Assign an exception to side_effect to make the mock raise it: mock_func.side_effect = ValueError('fail') Now, calling mock_func() raises ValueError. This is useful to test how your code handles failures.
Result
mock_func() raises ValueError('fail') instead of returning a value.
Simulating errors with mocks helps ensure your code handles problems gracefully.
6
AdvancedCombining Return Values and Side Effects
🤔Before reading on: can a mock have both return_value and side_effect set at the same time? What happens? Commit to your answer.
Concept: When side_effect is set, it overrides return_value. Understanding this prevents confusion in tests.
If you set both return_value and side_effect, the mock uses side_effect and ignores return_value. For example: mock_func.return_value = 10 mock_func.side_effect = lambda: 20 mock_func() returns 20, not 10. This means side_effect has higher priority.
Result
mock_func() returns 20, ignoring return_value 10.
Knowing the priority of side_effect over return_value avoids subtle bugs in tests.
7
ExpertMocking Async Functions with Side Effects
🤔Before reading on: do you think mocking async functions differs from regular ones? How? Commit to your answer.
Concept: Mocking async functions requires async-aware side effects to simulate awaitable behavior correctly.
For async functions, side_effect must be an async function or coroutine. For example: import asyncio from unittest.mock import AsyncMock async def async_side_effect(): await asyncio.sleep(0.01) return 'async result' mock_async = AsyncMock() mock_async.side_effect = async_side_effect Calling await mock_async() returns 'async result'. This ensures tests handle async code properly.
Result
await mock_async() returns 'async result' after a short delay.
Understanding async mocking prevents test failures and simulates real async behavior.
Under the Hood
Mocks in pytest (via unittest.mock) create proxy objects that intercept calls. When you set return_value, the mock stores it and returns it immediately on call. When you set side_effect, the mock calls the side_effect function or raises the exception instead of returning a fixed value. Internally, the mock's __call__ method checks side_effect first, then return_value. For lists as side_effect, the mock iterates through them, raising StopIteration when exhausted.
Why designed this way?
This design gives maximum flexibility: simple fixed returns for easy cases, and dynamic side effects for complex scenarios. Prioritizing side_effect over return_value avoids ambiguity. Supporting lists and exceptions as side_effects lets tests simulate sequences and errors naturally. Async mocks were added later to support modern async Python code, reflecting evolving language features.
┌───────────────┐
│   Mock Call   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Check side_effect │
│ - If function: call it
│ - If list: pop next
│ - If exception: raise it
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Else return_value │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: If you set both return_value and side_effect, which one does the mock use? Commit to your answer.
Common Belief:People often think return_value and side_effect combine or that return_value takes priority.
Tap to reveal reality
Reality:side_effect always takes priority over return_value. If side_effect is set, return_value is ignored.
Why it matters:Tests may behave unexpectedly if you assume return_value works when side_effect is set, causing confusion and wrong test results.
Quick: Does setting side_effect to a list mean the mock returns the whole list at once? Commit to your answer.
Common Belief:Some believe the mock returns the entire list as one value on each call.
Tap to reveal reality
Reality:The mock returns one item from the list per call, moving through the list sequentially.
Why it matters:Misunderstanding this leads to tests that fail because they expect a list but get single items instead.
Quick: Can you use side_effect to simulate async functions by assigning a normal function? Commit to your answer.
Common Belief:Many think any function assigned to side_effect works for async mocks.
Tap to reveal reality
Reality:For async mocks, side_effect must be an async function or coroutine; otherwise, tests fail or behave incorrectly.
Why it matters:Incorrect async mocking causes flaky tests or runtime errors, hiding real bugs.
Quick: Does a mock with a side_effect that raises an exception stop the test immediately? Commit to your answer.
Common Belief:People often think the test stops as soon as the mock raises an exception.
Tap to reveal reality
Reality:The exception is raised where the mock is called, and the test only stops if the exception is not caught or handled.
Why it matters:Misunderstanding this can lead to missing error handling tests or unexpected test failures.
Expert Zone
1
Mocks can track call arguments and call counts even when using side_effect, allowing detailed verification of interactions.
2
Using side_effect with generators or iterators can simulate streaming data or paginated responses realistically.
3
AsyncMock's side_effect can be a coroutine function or an iterable of coroutine results, enabling complex async test scenarios.
When NOT to use
Avoid using mocks with return values or side effects when testing integration or end-to-end flows where real components' behavior matters. Instead, use real instances or test doubles that mimic real behavior more closely, like fakes or stubs.
Production Patterns
In production tests, mocks with side_effect are used to simulate API failures, database errors, or timeouts. Return values are used to simulate normal responses. Combining both helps test retry logic, fallback mechanisms, and error handling comprehensively.
Connections
Dependency Injection
Mocks rely on dependency injection to replace real components with fakes during tests.
Understanding dependency injection helps you see how mocks fit into test design by allowing easy substitution of parts.
Fault Injection Testing
Side effects that raise exceptions simulate faults, connecting mocking to fault injection techniques.
Knowing fault injection helps appreciate how mocks test error handling and system resilience.
Theatre Acting
Mocks act as stand-ins or understudies, performing roles so the main actors (real components) can be tested indirectly.
This connection shows how mocks help isolate and rehearse parts of a system without the full cast.
Common Pitfalls
#1Setting both return_value and side_effect expecting both to work.
Wrong approach:mock_func.return_value = 5 mock_func.side_effect = lambda: 10 result = mock_func() # Expect 5 but gets 10
Correct approach:mock_func.side_effect = lambda: 10 result = mock_func() # Returns 10 as expected
Root cause:Misunderstanding that side_effect overrides return_value causes unexpected return values.
#2Using a normal function as side_effect for an async mock.
Wrong approach:mock_async.side_effect = lambda: 'value' await mock_async() # Fails or returns coroutine object
Correct approach:async def async_side_effect(): return 'value' mock_async.side_effect = async_side_effect await mock_async() # Returns 'value' correctly
Root cause:Not recognizing async mocks require async functions for side_effect leads to test errors.
#3Assigning a list to side_effect but expecting the entire list on each call.
Wrong approach:mock_func.side_effect = [1, 2, 3] print(mock_func()) # Expects [1,2,3], gets 1
Correct approach:mock_func.side_effect = [1, 2, 3] print(mock_func()) # Gets 1 print(mock_func()) # Gets 2
Root cause:Confusing side_effect list behavior with return_value causes wrong test expectations.
Key Takeaways
Mocks let you replace real functions or objects in tests to control their behavior.
Setting return_value gives a fixed output, while side_effect allows dynamic actions like raising errors or returning different values per call.
Side_effect overrides return_value, so setting both can cause unexpected results.
For async functions, side_effect must be async to simulate awaitable behavior correctly.
Using mocks with return values and side effects helps test code paths that are hard to reach with real dependencies.