Bird
Raised Fist0
PyTesttesting~15 mins

Mock return values and side effects in PyTest - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What does setting mock.return_value do in a pytest mock object?
easy
A. It delays the mock response by a set time.
B. It defines a fixed value that the mock returns when called.
C. It records the number of times the mock was called.
D. It raises an exception when the mock is called.

Solution

  1. Step 1: Understand the role of return_value

    The return_value attribute sets a fixed return value for the mock when it is called.
  2. Step 2: Differentiate from other mock features

    Unlike side_effect which can raise exceptions or return different values, return_value always returns the same fixed value.
  3. Final Answer:

    It defines a fixed value that the mock returns when called. -> Option B
  4. Quick Check:

    return_value = fixed return [OK]
Hint: return_value sets fixed output for mock calls [OK]
Common Mistakes:
  • Confusing return_value with side_effect
  • Thinking return_value raises exceptions
  • Assuming return_value tracks call count
2. Which of the following is the correct way to set a mock to raise an exception when called?
easy
A. mock.side_effect = Exception('Error')
B. mock.return_value = Exception('Error')
C. mock.raise_exception = Exception('Error')
D. mock.throw = Exception('Error')

Solution

  1. Step 1: Identify how to raise exceptions with mocks

    Setting side_effect to an exception causes the mock to raise that exception when called.
  2. Step 2: Check syntax correctness

    Only mock.side_effect = Exception('Error') is valid syntax to raise an exception on call.
  3. Final Answer:

    mock.side_effect = Exception('Error') -> Option A
  4. Quick Check:

    side_effect raises exceptions [OK]
Hint: Use side_effect to raise exceptions in mocks [OK]
Common Mistakes:
  • Using return_value to raise exceptions
  • Using non-existent attributes like raise_exception
  • Confusing syntax for exception raising
3. What will be the output of this pytest mock code?
from unittest.mock import Mock
mock = Mock()
mock.side_effect = [1, 2, 3]
print(mock())
print(mock())
print(mock())
medium
A. 1 2 3
B. [1, 2, 3] [1, 2, 3] [1, 2, 3]
C. None None None
D. Error on third call

Solution

  1. Step 1: Understand side_effect as a list

    When side_effect is a list, each call to the mock returns the next item in the list.
  2. Step 2: Trace each call output

    First call returns 1, second returns 2, third returns 3, so printed outputs are 1, 2, 3.
  3. Final Answer:

    1 2 3 -> Option A
  4. Quick Check:

    side_effect list returns items in order [OK]
Hint: side_effect list returns next item each call [OK]
Common Mistakes:
  • Expecting entire list returned each call
  • Confusing return_value with side_effect list
  • Assuming calls after list ends raise error
4. Identify the error in this pytest mock setup:
from unittest.mock import Mock
mock = Mock()
mock.side_effect = Exception('Fail')
mock.side_effect = [1, 2, 3]
print(mock())
medium
A. side_effect cannot be assigned twice
B. Exception object should be raised, not assigned to side_effect
C. The second assignment overwrites the first, no error occurs
D. Mock must be called before setting side_effect

Solution

  1. Step 1: Analyze multiple side_effect assignments

    The second assignment to side_effect replaces the first one; no error occurs.
  2. Step 2: Understand mock call behavior

    After setting side_effect = [1, 2, 3], the mock returns 1 on first call, so print(mock()) outputs 1.
  3. Final Answer:

    The second assignment overwrites the first, no error occurs -> Option C
  4. Quick Check:

    Last side_effect assignment wins [OK]
Hint: Last side_effect assignment replaces previous ones [OK]
Common Mistakes:
  • Thinking multiple side_effect assignments cause errors
  • Expecting exception to be raised immediately
  • Confusing assignment with raising exceptions
5. You want a mock to return 10 on the first call, raise a ValueError on the second, and return 20 on the third call. Which setup achieves this?
hard
A. mock.side_effect = [10, ValueError('bad'), 20]
B. mock.return_value = 10 mock.side_effect = ValueError('bad') mock.return_value = 20
C. mock.side_effect = lambda: [10, ValueError('bad'), 20]
D. mock.side_effect = [10, lambda: (_ for _ in ()).throw(ValueError('bad')), 20]

Solution

  1. Step 1: Understand side_effect list behavior

    When side_effect is a list, the mock returns successive items from the list on each call. Non-callable items like numbers or exception instances are returned directly.
  2. Step 2: Recognize exception raising in side_effect list

    Placing an exception instance directly returns it as a value, not raises it. To raise, use a generator expression that throws the exception.
  3. Step 3: Analyze options

    mock.side_effect = [10, ValueError('bad'), 20] returns the exception object instead of raising it. mock.side_effect = [10, lambda: (_ for _ in ()).throw(ValueError('bad')), 20] uses a generator expression to raise ValueError on second call, returning 10 and 20 on first and third calls respectively.
  4. Final Answer:

    mock.side_effect = [10, lambda: (_ for _ in ()).throw(ValueError('bad')), 20] -> Option D
  5. Quick Check:

    Use generator throw to raise exceptions in side_effect list [OK]
Hint: Use generator throw to raise exceptions in side_effect list [OK]
Common Mistakes:
  • Putting exception instance directly in side_effect list
  • Trying to set multiple return_values
  • Using lambda returning list instead of generator throw
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard