如何使用unittest模拟flask.request?

时间:2019-06-18 10:04:40

标签: python flask mocking python-import python-unittest

在我的应用程序中,我有一个从flask服务调用的类。该类从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方法中的模拟没有被应用。

这可以通过仅导入flask并访问flask.request.path之类的path属性来解决。但我想避免导入完整的flask模块。

有什么方法可以为使用flask.request中的属性的方法创建单元测试,模拟它们而无需使用flask测试客户端吗?

1 个答案:

答案 0 :(得分:0)

必须有一种方法,但是像这样的单元测试代码仍然会给您带来麻烦。 SUT正在访问由另一个模块管理的全局状态,因此您的测试需要正确地设置该全局状态,或者通过按原样使用该其他模块,而这是您出于充分理由不希望使用的(此外,或通过猴子修补来进行单元测试,这通常很棘手(如您所知)并且很脆弱(如果您更改在生产代码中导入内容的方式,则您的测试将失败;如果相关,行为没有改变?)

解决此类问题的方法是使您的对象问他们需要的东西,而不是在全局状态下寻找它们。因此,如果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了,我将其保留在那里是为了减少与原始代码的偏离。我假设您还有其他测试可以说明为什么真正需要它。