如何用 unittest 模拟 flask.request?
How to mock flask.request with unittest?
在我的应用程序中,我有一个从烧瓶服务调用的 class。这个 class 从 flask.request 对象中获取了一些属性,所以我想模拟它们。
我的实施示例是:
myClassHelper.py
from flask import request
class MyClassHelper:
def __init__(self, addRequestData=False):
self.attribute = 'something'
self.path = request.path if addRequestData else None
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
myClassHelperTest.py
from unittest import TestCase
from unittest.mock import MagicMock
import flask
from myClassHelper import MyClassHelper
class MyClassHelperTest(TestCase):
def setUp(self):
self.path = '/path'
self.unmock = {}
self.unmock['flask.request'] = flask.request
flask.request = MagicMock(path='/path')
def tearDown(self):
flask.request = self.unmock['flask.request']
def test_printAttributes(self):
expectedResult = 'attribute=something; path={0};'.format(self.path)
result = str(MyClassHelper(addRequestData=True))
self.assertEqual(expectedResult, result)
导入时出现问题from myClassHelper import MyClassHelper
。这将转到 MyClassHelper 中的导入 from flask import request
。所以测试的setUp方法中的mock class,它没有被应用。
这可以通过导入 flask 并像 flask.request.path
一样访问路径属性来解决。但我想避免导入完整的烧瓶模块。
有没有办法为使用 flask.request 中的属性的方法创建单元测试,模拟它们而不使用烧瓶测试客户端?
肯定有办法,但是像这样的单元测试代码无论如何都会给您带来麻烦。 SUT 正在访问由另一个模块管理的全局状态,因此您的测试需要正确设置该全局状态。这可以通过按原样使用另一个模块来完成,你有充分的理由不想要它(加上它不再是单元测试),或者通过 monkey-patching 它。这通常很棘手(正如您已经发现的那样)并且很脆弱(如果您更改在生产代码中导入内容的方式,您的测试将会中断;如果相关行为没有改变,为什么会发生这种情况?)
解决此类问题的方法是让您的对象请求它们需要的东西,而不是在全局状态中寻找它们。因此,如果 MyClassHelper
的所有实例都需要一条路径,只需让它请求一条路径即可。让调用代码弄清楚路径应该从哪里来。具体来说,您的测试可以轻松提供固定路径。
如果您遵循此原则,您的测试将是这样的:
class MyClassHelperTest(TestCase):
def test_printAttributes(self):
expectedResult = 'attribute=something; path=/path;'
result = str(MyClassHelper('/path'))
self.assertEqual(expectedResult, result)
比以前简单多了。这就是你让它通过的方式:
class MyClassHelper:
def __init__(self, path):
self.attribute = 'something'
self.path = path
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
如果测试中的行为就是您想要的,那么您并不需要 attribute
。我把它留在那里是为了减少与您的原始代码的偏差。我假设您有其他测试可以说明为什么实际需要它。
在我的应用程序中,我有一个从烧瓶服务调用的 class。这个 class 从 flask.request 对象中获取了一些属性,所以我想模拟它们。
我的实施示例是:
myClassHelper.py
from flask import request
class MyClassHelper:
def __init__(self, addRequestData=False):
self.attribute = 'something'
self.path = request.path if addRequestData else None
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
myClassHelperTest.py
from unittest import TestCase
from unittest.mock import MagicMock
import flask
from myClassHelper import MyClassHelper
class MyClassHelperTest(TestCase):
def setUp(self):
self.path = '/path'
self.unmock = {}
self.unmock['flask.request'] = flask.request
flask.request = MagicMock(path='/path')
def tearDown(self):
flask.request = self.unmock['flask.request']
def test_printAttributes(self):
expectedResult = 'attribute=something; path={0};'.format(self.path)
result = str(MyClassHelper(addRequestData=True))
self.assertEqual(expectedResult, result)
导入时出现问题from myClassHelper import MyClassHelper
。这将转到 MyClassHelper 中的导入 from flask import request
。所以测试的setUp方法中的mock class,它没有被应用。
这可以通过导入 flask 并像 flask.request.path
一样访问路径属性来解决。但我想避免导入完整的烧瓶模块。
有没有办法为使用 flask.request 中的属性的方法创建单元测试,模拟它们而不使用烧瓶测试客户端?
肯定有办法,但是像这样的单元测试代码无论如何都会给您带来麻烦。 SUT 正在访问由另一个模块管理的全局状态,因此您的测试需要正确设置该全局状态。这可以通过按原样使用另一个模块来完成,你有充分的理由不想要它(加上它不再是单元测试),或者通过 monkey-patching 它。这通常很棘手(正如您已经发现的那样)并且很脆弱(如果您更改在生产代码中导入内容的方式,您的测试将会中断;如果相关行为没有改变,为什么会发生这种情况?)
解决此类问题的方法是让您的对象请求它们需要的东西,而不是在全局状态中寻找它们。因此,如果 MyClassHelper
的所有实例都需要一条路径,只需让它请求一条路径即可。让调用代码弄清楚路径应该从哪里来。具体来说,您的测试可以轻松提供固定路径。
如果您遵循此原则,您的测试将是这样的:
class MyClassHelperTest(TestCase):
def test_printAttributes(self):
expectedResult = 'attribute=something; path=/path;'
result = str(MyClassHelper('/path'))
self.assertEqual(expectedResult, result)
比以前简单多了。这就是你让它通过的方式:
class MyClassHelper:
def __init__(self, path):
self.attribute = 'something'
self.path = path
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
如果测试中的行为就是您想要的,那么您并不需要 attribute
。我把它留在那里是为了减少与您的原始代码的偏差。我假设您有其他测试可以说明为什么实际需要它。