python / python/cpython

Unittest.mock.patch - record result of wrapped function call

Open
#130,368 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stdlib type-feature
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Feature or enhancement

Proposal:

I would like to access the return value of a function call wrapped with unittest.mock.patch, that I don't call myself directly.

Motivation

Let's say I have a framework that supports some "before" hooks to allow users to customize some data flowing through the framework.
One could write hook functions for such a framework and publish them.
When doing so, it would make integration tests easier if one could access the return value of a function wrapped with a MagicMock.

# framework.py
def init(before_hook):
    ... # register the update hook that is later used before doing some action to allow customization of the action

# my_plugin.py
def my_before_hook(some_data: dict) -> dict:
    ... #do something with some_data eg adding a key
    some_data["foo"] = "bar"
    return some_data # return the altered data


# test_my_plugin.py
import my_plugin
import framework

def test_my_before_hook():
    with unittest.mock.patch("my_plugin.my_before_hook",  wraps=my_plugin.my_before_hook) as m:
        framework.init(before_hook=m)
        ... #do something that runs the framework
        assert m.called
        assert ... # assert m's return value has the key "foo" with the value "bar"

Unfortunately, currently the second assert statement is not really possible - at least I couldn't find anything in the docs, debugger, and LLMs just hallucinated on this one 😅.

Alternatives

There are two alternatives I have considered:

  1. Digging into the framework and finding a mock target - while this would work, it can be quite tedious, especially when the framework is big
  2. Wrapping the function in question manually - Definitely possible, but seems to introduce an extra step every time this is necessary and moves the assertion to a an odd place:
# test_my_plugin.py
import my_plugin
import framework

def foo(*args, **kwargs)
    result = my_plug.my_before_hook(*args, **kwargs)
    assert result["foo"] == "bar" # this only works if the framework doesn't handle AssertionErrors
    return result


def test_my_before_hook():
    with unittest.mock.patch("my_plugin.my_before_hook",  wraps=foo) as m:
        framework.init(before_hook=m)
        ... #do something that runs the framework
        assert m.called

Proposed Solution

Record the return value of a "wraps" call and make it available on the call tuple.

I know this could be somewhat problematic due to backwards compatibility, but I'm not entirely sure.


I dove a little bit into the source code and don't think it will be too big of a change both in terms of code and functionality - please correct me if I'm wrong.

I'd be happy to contribute if this is enhancement is desired 🙂

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing unittest.mock.patch, MagicMock, wraps, and call tuple behavior described by the issue, then inspect the relevant unittest.mock implementation and tests. Determine how a wrapped function's return value could be exposed without breaking the current call API. Done means the behavior is specified, implemented, and covered by tests for wrapped calls and compatibility.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
testing-qa
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.