python mock - 在不妨碍实现的情况下修补方法

时间:2014-09-01 14:33:54

标签: python mocking

是否有一种干净的方法来修补对象,以便在测试用例中获得assert_call*帮助程序,而不实际删除操作?

例如,如何修改@patch行以获得以下测试通过:

from unittest import TestCase
from mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    @patch.object(Potato, 'foo')
    def test_something(self, mock):
        spud = Potato()
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)

我可能会使用side_effect一起破解这个,但我希望有更好的方法在所有函数,类方法,静态方法,未绑定方法等上以相同的方式工作。

2 个答案:

答案 0 :(得分:29)

与您的类似解决方案,但使用wraps

def test_something(self):
    spud = Potato()
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
    self.assertEqual(forty_two, 42)

根据the documentation

  

包装:要包装的模拟对象的项目。如果换行不是无。那么   调用Mock会将调用传递给包装对象   (返回实际结果)。模拟的属性访问将返回   一个Mock对象,它包装了包装的相应属性   对象(因此尝试访问不存在的属性将会   引发一个AttributeError)。


class Potato(object):

    def spam(self, n):
        return self.foo(n=n)

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    def test_something(self):
        spud = Potato()
        with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
            forty_two = spud.spam(n=40)
            mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)

答案 1 :(得分:7)

这个答案解决了用户Quuxplusone的赏金中提到的额外要求:

  

对我的用例来说重要的是它与@patch.mock一起使用,即它不需要我在构造Potato实例之间插入任何代码({{1}在这个例子中)和我对spud的调用。我需要从一开始就使用模拟spud.foo方法创建spud,因为我无法控制创建foo的位置。

使用装饰器可以毫不费力地实现上述用例:

spud

如果替换的方法接受在测试中修改的可变参数,您可能希望初始化CopyingMock * 来代替spy_decorator中的import unittest import unittest.mock # Python 3 def spy_decorator(method_to_decorate): mock = unittest.mock.MagicMock() def wrapper(self, *args, **kwargs): mock(*args, **kwargs) return method_to_decorate(self, *args, **kwargs) wrapper.mock = mock return wrapper def spam(n=42): spud = Potato() return spud.foo(n=n) class Potato(object): def foo(self, n): return self.bar(n) def bar(self, n): return n + 2 class PotatoTest(unittest.TestCase): def test_something(self): foo = spy_decorator(Potato.foo) with unittest.mock.patch.object(Potato, 'foo', foo): forty_two = spam(n=40) foo.mock.assert_called_once_with(n=40) self.assertEqual(forty_two, 42) if __name__ == '__main__': unittest.main()

* 这是我从docs发布的关于PyPI的copyingmock lib

的食谱