使用 Python Mock 模拟函数

我正在尝试使用 python模拟模块返回一个函数(返回一些外部内容)。

I'm having some trouble mocking functions that are imported into a module.

例如,在 util.py中,我有

def get_content():
return "stuff"

我想模拟 util.get_content,以便它返回其他的值。

我正在尝试:

util.get_content=Mock(return_value="mocked stuff")

如果在另一个模块中调用 get_content,它实际上似乎永远不会返回模拟对象。在如何使用 Mock方面,我是否遗漏了什么?

请注意,如果我调用以下命令,那么事情就会正常运行:

>>> util.get_content=Mock(return_value="mocked stuff")
>>> util.get_content()
"mocked stuff"

但是,如果从另一个模块内部调用 get_content,它将调用原始函数而不是模拟版本:

>>> from mymodule import MyObj
>>> util.get_content=Mock(return_value="mocked stuff")
>>> m=MyObj()
>>> m.func()
"stuff"

mymodule.py的内容

from util import get_content


class MyObj:
def func():
get_content()

所以我想我的问题是——我如何从我调用的模块中调用函数的 Mocked 版本?

看起来 from module import function可能是这里的罪魁祸首,因为它没有指向 Mocked 函数。

103884 次浏览

Let's assume you're creating your mock inside module foobar:

import util, mock
util.get_content = mock.Mock(return_value="mocked stuff")

If you import mymodule and call util.get_content without first importing foobar, your mock will not be installed:

import util
def func()
print util.get_content()
func()
"stuff"

Instead:

import util
import foobar   # substitutes the mock
def func():
print util.get_content()
func()
"mocked stuff"

Note that foobar can be imported from anywhere (module A imports B which imports foobar) as long as foobar is evaluated before util.get_content is called.

I think I have a workaround, though it's still not quite clear on how to solve the general case

In mymodule, if I replace

from util import get_content


class MyObj:
def func():
get_content()

with

import util


class MyObj:
def func():
util.get_content()

The Mock seems to get invoked. It looks like the namespaces need to match (which makes sense). However, the weird thing is that I would expect

import mymodule
mymodule.get_content = mock.Mock(return_value="mocked stuff")

to do the trick in the original case where I am using the from/import syntax (which now pulls in get_content into mymodule). But this still refers to the unmocked get_content.

Turns out the namespace matters - just need to keep that in mind when writing your code.

You have to patch the function where it is being used. In your case that would be in the mymodule module.

import mymodule
>>> mymodule.get_content = Mock(return_value="mocked stuff")
>>> m = mymodule.MyObj()
>>> m.func()
"mocked stuff"

There is a reference in the docs here: http://docs.python.org/dev/library/unittest.mock.html#where-to-patch

While it doesn't provide an answer to your question directly, another possible alternative is to transform your function to a static method using the @staticmethod.

So you could transform your module utils into a class using something like:

class util(object):
@staticmethod
def get_content():
return "stuff"

Then mock patches it correctly.

The general case would be to use patch from mock. Consider the following:

utils.py

def get_content():
return 'stuff'

mymodule.py

from util import get_content




class MyClass(object):


def func(self):
return get_content()

test.py

import unittest


from mock import patch


from mymodule import MyClass


class Test(unittest.TestCase):


@patch('mymodule.get_content')
def test_func(self, get_content_mock):
get_content_mock.return_value = 'mocked stuff'


my_class = MyClass()
self.assertEqual(my_class.func(), 'mocked stuff')
self.assertEqual(get_content_mock.call_count, 1)
get_content_mock.assert_called_once()

Note how get_content is mocked, it is not util.get_content, rather mymodule.get_content since we are using it in mymodule.

Above has been tested with mock v2.0.0, nosetests v1.3.7 and python v2.7.9.