我有一个我正在测试的类,它具有作为依赖的另一个类(其实例被传递给CUT的init方法)。我想使用Python Mock库来模拟这个类。
我所拥有的是:
mockobj = Mock(spec=MyDependencyClass)
mockobj.methodfromdepclass.return_value = "the value I want the mock to return"
assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return")
cutobj = ClassUnderTest(mockobj)
哪个好,但是“methodfromdepclass”是一个参数化方法,因此我想创建一个模拟对象,根据传递给methodfromdepclass的参数,它返回不同的值。
我想要这个参数化行为的原因是我想创建包含不同值的ClassUnderTest的多个实例(其值由mockobj返回的值生成)。
有点我在想什么(当然这不起作用):
mockobj = Mock(spec=MyDependencyClass)
mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42"
mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99"
assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42")
assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99")
cutinst1 = ClassUnderTest(mockobj, 42)
cutinst2 = ClassUnderTest(mockobj, 99)
# now cutinst1 & cutinst2 contain different values
如何实现这种“ifcalledwith”类型的语义?
答案 0 :(得分:71)
尝试side_effect
def my_side_effect(*args, **kwargs):
if args[0] == 42:
return "Called with 42"
elif args[0] == 43:
return "Called with 43"
elif kwarg['foo'] == 7:
return "Foo is seven"
mockobj.mockmethod.side_effect = my_side_effect
答案 1 :(得分:47)
有点甜:
mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x]
或多个参数:
mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x]
或使用默认值:
mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000)
或两者兼而有之:
mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000)
并且高兴地我们走了。
答案 2 :(得分:9)
当我进行自己的测试时,我遇到了这个问题。如果你不关心从methoddepclass()捕获对你的方法的调用,但只需要它返回一些东西,那么以下就足够了:
def makeFakeMethod(mapping={}):
def fakeMethod(inputParam):
return mapping[inputParam] if inputParam in mapping else MagicMock()
return fakeMethod
mapping = {42:"Called with 42", 59:"Called with 59"}
mockobj.methodfromdepclass = makeFakeMethod(mapping)
这是一个参数化版本:
def makeFakeMethod():
def fakeMethod(param):
return "Called with " + str(param)
return fakeMethod
答案 3 :(得分:0)
与here中一样,除了在unittest.mock.Mock中使用side_effect
之外,您还可以将@mock.patch.object
与new_callable
一起使用,这允许您修补具有模拟对象的对象。
假设模块my_module.py
使用pandas
从数据库中读取数据,我们希望通过模拟pd.read_sql_table
方法(以table_name
作为参数)来测试该模块
您可以做的是在测试内部创建一个db_mock
方法,该方法根据提供的参数返回不同的对象:
def db_mock(**kwargs):
if kwargs['table_name'] == 'table_1':
# return some DataFrame
elif kwargs['table_name'] == 'table_2':
# return some other DataFrame
然后在测试功能中执行以下操作:
import my_module as my_module_imported
@mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock)
def test_my_module(mock_read_sql_table):
# You can now test any methods from `my_module`, e.g. `foo` and any call this
# method does to `read_sql_table` will be mocked by `db_mock`, e.g.
ret = my_module_imported.foo(table_name='table_1')
# `ret` is some DataFrame returned by `db_mock`