【问题标题】:Pytest: Mock multiple calls of same method with different side_effectPytest:模拟具有不同副作用的同一方法的多个调用
【发布时间】:2021-03-07 13:30:32
【问题描述】:

我有一个像下面这样的单元测试:

# utilities.py  
def get_side_effects():
    def side_effect_func3(self):
        # Need the "self" to do some stuff at run time.
        return {"final":"some3"} 

    def side_effect_func2(self):
        # Need the "self" to do some stuff at run time.
        return {"status":"some2"}
      
    def side_effect_func1(self):
        # Need the "self" to do some stuff at run time.
        return {"name":"some1"} 

    return side_effect_func1, side_effect_func2, side_effect_func2

#################

# test_a.py
def test_endtoend():
   
    s1, s2, s3 = utilities.get_side_effects()
    
    m1 = mock.MagicMock()
    m1.side_effect = s1

    m2 = mock.MagicMock()
    m2.side_effect = s2

    m3 = mock.MagicMock()
    m3.side_effect = s3
   
    with mock.patch("a.get_request", m3):
        with mock.patch("a.get_request", m2):
            with mock.patch("a.get_request", m1):
                foo = a() # Class to test
                result = foo.run() 
    
    

作为foo.run() 代码运行的一部分,get_request 被多次调用。我想为get_request 方法的每个调用设置不同的副作用函数,在本例中为side_effect_func1side_effect_func2side_effect_func3。但我注意到只有 m1 模拟对象处于活动状态,即只有 side_effect_func1 被调用,而不是其他 2。我该如何实现?

我也尝试了下面的方法,但实际的副作用函数没有被调用,它们总是返回function object,但实际上并不执行副作用函数。

# utilities.py
def get_side_effects():
    def side_effect_func3(self):
        # Need the "self" to do some stuff at run time.
        return {"final":"some3"} 

    def side_effect_func2(self):
        # Need the "self" to do some stuff at run time.
        return {"status":"some2"}
      
    def side_effect_func1(self):
        # Need the "self" to do some stuff at run time.
        return {"name":"some1"} 

    all_get_side_effects = []
    all_get_side_effects.append(side_effect_func1)
    all_get_side_effects.append(side_effect_func2)
    all_get_side_effects.append(side_effect_func3)
     
    return all_get_side_effects

#########################
# test_a.py
def test_endtoend():

    all_side_effects = utilities.get_side_effects()

    m = mock.MagicMock()
    m.side_effect = all_side_effects

    with mock.patch("a.get_request", m):
       foo = a() # Class to test
       result = foo.run()

【问题讨论】:

    标签: python mocking pytest side-effects


    【解决方案1】:

    您的第一次尝试不起作用,因为每个模拟都替换了前一个模拟(外部两个模拟不做任何事情)。

    您的第二次尝试不起作用,因为副作用被重载以服务于可迭代对象的不同目的 (docs):

    如果side_effect 是一个可迭代对象,那么对模拟的每次调用都会从可迭代对象返回下一个值。

    相反,您可以使用一个可调用类来实现副作用,即连续保持一些关于实际调用哪个底层函数的状态。

    具有两个功能的基本示例:

    >>> class SideEffect:
    ...     def __init__(self, *fns):
    ...         self.fs = iter(fns)
    ...     def __call__(self, *args, **kwargs):
    ...         f = next(self.fs)
    ...         return f(*args, **kwargs)
    ... 
    >>> def sf1():
    ...     print("called sf1")
    ...     return 1
    ... 
    >>> def sf2():
    ...     print("called sf2")
    ...     return 2
    ... 
    >>> def foo():
    ...     print("called actual func")
    ...     return "f"
    ... 
    >>> with mock.patch("__main__.foo", side_effect=SideEffect(sf1, sf2)) as m:
    ...     first = foo()
    ...     second = foo()
    ... 
    called sf1
    called sf2
    >>> assert first == 1
    >>> assert second == 2
    >>> assert m.call_count == 2
    

    【讨论】:

    • 感谢您的解释!我用你的答案对我的实际代码进行了改进,它奏效了。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-03
    • 2011-07-25
    • 2023-01-27
    • 2022-01-06
    • 2021-11-10
    • 2011-10-17
    • 1970-01-01
    相关资源
    最近更新 更多