【问题标题】:Mock default attribute of SQLAlchemySQLAlchemy 的模拟默认属性
【发布时间】:2019-06-13 00:26:33
【问题描述】:

在我的模型中使用 defaultonupdate 字段时,我在模拟 SQLAlchemy 对象时遇到了一些问题:

def get_uuid():
    return str(uuid.uuid4())

def get_now():
    return db.func.now()

class BaseModel(db.Model):
    __abstract__ = True

    id = db.Column(UUIDType(binary=False), primary_key=True, nullable=False, default=get_uuid)
    created_at = db.Column(db.DateTime(timezone=True), default=get_now(), nullable=False, index=True)

get_now()get_uuid() 的行为即使在我尝试在测试中模拟它们时也不会改变:

def test_create_source(client, mocker):

    mock = mocker.MagicMock(return_value='123e4567-e89b-12d3-a456-426655440000')
    mocker.patch('myproject.models.get_uuid', mock)
    mock = mocker.MagicMock(return_value=datetime.datetime(2019, 1, 1))
    mocker.patch('myproject.models.get_now', mock)

    resp = client.post('/sources', json={'name': 'My source'})
    assert resp.json == {
        'name': 'My source',
        'id': '123e4567-e89b-12d3-a456-426655440000',
        'createdAt': 'Tue, 01 Jan 2019 00:00:00 GMT',
        'updatedAt': 'Tue, 01 Jan 2019 00:00:00 GMT'
    }

### Results :

>       assert resp.json == {
            'name': 'My source',
            'id': '123e4567-e89b-12d3-a456-426655440000',
            'createdAt': 'Tue, 01 Jan 2019 00:00:00 GMT',
            'updatedAt': 'Tue, 01 Jan 2019 00:00:00 GMT'
        }
E       AssertionError: assert {'createdAt':...17:38:38 GMT'} == {'createdAt': ...00:00:00 GMT'}
E         Omitting 1 identical items, use -vv to show
E         Differing items:
E         {'id': '8eb074c0-41e9-436c-8f71-b4c6842f4809'} != {'id': '123e4567-e89b-12d3-a456-426655440000'}
E         {'createdAt': 'Fri, 18 Jan 2019 17:38:38 GMT'} != {'createdAt': 'Tue, 01 Jan 2019 00:00:00 GMT'}
E         {'updatedAt': 'Fri, 18 Jan 2019 17:38:38 GMT'} != {'updatedAt': 'Tue, 01 Jan 2019 00:00:00 GMT'}
E         Use -v to get the full diff

tests/test_sources.py:17: AssertionError

我认为这是因为我的模型及其属性在进行测试之前已经被导入和评估,所以模拟在这里没有用。 post 的“模拟类助手”部分对此进行了解释,但我仍然无法解决我的问题 :(

重现该问题的完整可运行代码可在此处获得:https://github.com/ncrocfer/flaskmock

请问你有什么想法吗?

【问题讨论】:

    标签: python testing flask sqlalchemy pytest


    【解决方案1】:

    我认为这是因为我的模型及其属性在进行测试之前已经被导入和评估,所以这里的 mock 没用。

    关闭:您已将函数get_uuid() 的默认引用传递给id,并将调用get_now()(函数表达式对象)的结果默认传递给created_at。事后更改这些名称在模块中绑定的内容不再对列产生影响,这些列已经包含对对象本身的引用。

    如果是get_uuid(),你应该模拟它正在调用的函数:

    mock = mocker.MagicMock(return_value='123e4567-e89b-12d3-a456-426655440000')
    mocker.patch('uuid.uuid4', mock)
    

    如果是get_now(),您应该考虑在模型构建期间不要调用它:

    class BaseModel(db.Model):
        ...
        created_at = db.Column(..., default=get_now, ...)
    

    这样您就可以再次模拟它正在调用的实际函数:

    mock = mocker.MagicMock(return_value=datetime.datetime(2019, 1, 1))
    mocker.patch('sqlalchemy.func.now', mock)
    

    另一方面,也许您不应该断言任何关于 id 和测试中的时间戳的信息,因为似乎重要的是验证名称是否设置正确。

    【讨论】:

    • 感谢您的回答和解释 Ilja !您的解决方案非常适合 uuid 模拟,但是当我在模型构建期间不调用 get_now 函数时,SQLAlchemy 似乎不喜欢:sqlalchemy.exc.StatementError: (builtins.TypeError) SQLite DateTime type only accepts Python datetime and date objects as input. [SQL: 'INSERT INTO sources (id, created_at, updated_at, name) VALUES (?, ?, ?, ?)'] [parameters: [{'name': 'My source'}]]
    • 这是我的疏忽,处理起来有点棘手。将不得不考虑一下。这是默认函数在执行期间产生的不是 Python datetime 的结果(SQLA 特殊情况在构造期间将 sql 表达式对象作为默认值传递,等等)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-04
    • 2019-02-25
    • 2012-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多