Patch __call__ of a function

后端 未结 3 1784
离开以前
离开以前 2020-12-11 00:44

I need to patch current datetime in tests. I am using this solution:

def _utcnow():
    return datetime.datetime.utcnow()


def utcnow():
    \"\"\"A proxy w         


        
3条回答
  •  难免孤独
    2020-12-11 00:58

    [EDIT]

    Maybe the most interesting part of this question is Why I cannot patch somefunction.__call__?

    Because the function don't use __call__'s code but __call__ (a method-wrapper object) use function's code.

    I don't find any well sourced documentation about that, but I can prove it (Python2.7):

    >>> def f():
    ...     return "f"
    ... 
    >>> def g():
    ...     return "g"
    ... 
    >>> f
    
    >>> f.__call__
    
    >>> g
    
    >>> g.__call__
    
    

    Replace f's code by g's code:

    >>> f.func_code = g.func_code
    >>> f()
    'g'
    >>> f.__call__()
    'g'
    

    Of course f and f.__call__ references are not changed:

    >>> f
    
    >>> f.__call__
    
    

    Recover original implementation and copy __call__ references instead:

    >>> def f():
    ...     return "f"
    ... 
    >>> f()
    'f'
    >>> f.__call__ = g.__call__
    >>> f()
    'f'
    >>> f.__call__()
    'g'
    

    This don't have any effect on f function. Note: In Python 3 you should use __code__ instead of func_code.

    I Hope that somebody can point me to the documentation that explain this behavior.

    You have a way to work around that: in utils you can define

    class Utcnow(object):
        def __call__(self):
            return datetime.datetime.utcnow()
    
    
    utcnow = Utcnow()
    

    And now your patch can work like a charm.


    Follow the original answer that I consider even the best way to implement your tests.

    I've my own gold rule: never patch protected methods. In this case the things are little bit smoother because protected method was introduced just for testing but I cannot see why.

    The real problem here is that you cannot to patch datetime.datetime.utcnow directly (is C extension as you wrote in the comment above). What you can do is to patch datetime by wrap the standard behavior and override utcnow function:

    >>> with mock.patch("datetime.datetime", mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=3))):
    ...  print(datetime.datetime.utcnow())
    ... 
    3
    

    Ok that is not really clear and neat but you can introduce your own function like

    def mock_utcnow(return_value):
        return mock.Mock(wraps=datetime.datetime, 
                         utcnow=mock.Mock(return_value=return_value)):
    

    and now

    mock.patch("datetime.datetime", mock_utcnow(***))
    

    do exactly what you need without any other layer and for every kind of import.

    Another solution can be import datetime in utils and to patch ***.utils.datetime; that can give you some freedom to change datetime reference implementation without change your tests (in this case take care to change mock_utcnow() wraps argument too).

提交回复
热议问题