Pytest:使用不同 side_effect 模拟同一方法的多次调用
Pytest: Mock multiple calls of same method with different side_effect
我有一个如下所示的单元测试:
# 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 函数,在本例中为 side_effect_func1
、side_effect_func2
、side_effect_func3
。但我注意到只有 m1
模拟对象处于活动状态,即仅调用 side_effect_func1
而不是其他 2 个。我如何实现这一点?
我也试过下面的方法,但是实际的 side_effect 函数没有被调用,它们总是 return function object
,但实际上不执行 side_effect 函数。
# 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()
您的第一次尝试没有成功,因为每个模拟只是替换了前一个(外面的两个模拟不做任何事情)。
您的第二次尝试无效,因为副作用超载以服务于可迭代对象的不同目的 (docs):
If side_effect
is an iterable then each call to the mock will return the next value from the iterable.
相反,您可以使用可调用的 class 作为副作用,它会连续保持一些关于实际调用哪个底层函数的状态。
具有两个函数的基本示例:
>>> 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
我有一个如下所示的单元测试:
# 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 函数,在本例中为 side_effect_func1
、side_effect_func2
、side_effect_func3
。但我注意到只有 m1
模拟对象处于活动状态,即仅调用 side_effect_func1
而不是其他 2 个。我如何实现这一点?
我也试过下面的方法,但是实际的 side_effect 函数没有被调用,它们总是 return function object
,但实际上不执行 side_effect 函数。
# 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()
您的第一次尝试没有成功,因为每个模拟只是替换了前一个(外面的两个模拟不做任何事情)。
您的第二次尝试无效,因为副作用超载以服务于可迭代对象的不同目的 (docs):
If
side_effect
is an iterable then each call to the mock will return the next value from the iterable.
相反,您可以使用可调用的 class 作为副作用,它会连续保持一些关于实际调用哪个底层函数的状态。
具有两个函数的基本示例:
>>> 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