使用伪造的mongoDB进行pytest测试


问题内容

我有连接到MongoDB客户端的代码,并且正在尝试对其进行测试。对于测试,我不想连接到实际的客户端,因此,我试图找出一个用于测试目的的假冒产品。代码的基本流程是,我在某处有一个函数,该函数创建一个pymongo客户端,然后对其进行查询并生成可在其他地方使用的字典。

我想使用pytest编写一些测试,这些测试将测试将调用的不同函数和类get_stuff。我的问题是get_stuff调用mongo()实际上是建立与数据库的连接的原因。我试图使用pytest.fixture(autouse=True)mongomock.MongoClient()替换mongo()

但这并不能取代mongo_stuff.mongo()。有什么办法可以告诉pytest替换一个函数,以便fixture调用我的函数而不是实际函数?我以为,这样做fixture会使我的测试mongo()在名称空间中的优先级高于实际模块中的功能。

这是带有我的示例的示例文件结构:

.
├── project
│   ├── __init__.py
│   ├── mongo_stuff
│   │   ├── __init__.py
│   │   └── mongo_stuff.py
│   └── working_class
│       ├── __init__.py
│       └── somewhere_else.py
└── testing
    ├── __init__.py
    └── test_stuff.py

mongo_stuff.py

import pymongo

def mongo():
    return pymongo.MongoClient(connection_params)

def get_stuff():
    db = mongo()  # Makes the connection using another function
    stuff = query_function(db)  # Does the query and makes a dict
    return result

某处_else.py

from project.mongo_stuff import mongo_stuff

mongo_dict = mongo_stuff.get_stuff()

test_stuff.py

import pytest
import mongomock

@pytest.fixture(autouse=True)
def patch_mongo(monkeypatch):
    db = mongomock.MongoClient()
    def fake_mongo():
        return db
    monkeypatch.setattr('project.mongo_stuff.mongo', fake_mongo)

from poject.working_class import working_class  # This starts by calling project.mongo_stuff.mongo_stuff.get_stuff()

由于 mongo_stuff.py*connection params中的 mongo_stuff.py 仅在生产环境中 可用,
因此这当前会给我一个连接错误。如果我将 test_stuff.py中
import语句放入测试函数中,则它可以正常工作,并且db将在测试环境中使用。我也尝试将更改为也不起作用。


*mongomock``setattr``monkeypatch.setattr('project.working_class.mongo_stuff.mongo', fake_mongo)


问题答案:

您已经到了一半:您已经为db客户端创建了一个模拟,现在您必须修补该mongo_stuff.mongo函数以返回该模拟,而不是真正的连接:

@pytest.fixture(autouse=True)
def patch_mongo(monkeypatch):
    db = mongomock.MongoClient()
    def fake_mongo():
        return db
    monkeypatch.setattr('mongo_stuff.mongo', fake_mongo)

编辑:

出现连接错误的原因是,您正在somewhere_else中的模块级别导入test_stuff,并且somewhere_else也在模块级别上运行连接代码。因此,用灯具打补丁太迟了,不会有任何效果。如果要在模块级别导入,则必须在导入
之前 修补mongo客户端somewhere_else。这样可以避免引发错误,但是非常丑陋:

from project.mongo_stuff import mongo_stuff
import mongomock
import pytest

from unittest.mock import patch

with patch.object(mongo_stuff, 'mongo', return_value=mongomock.MongoClient()):

    from project.working_class import somewhere_else


@patch.object(mongo_stuff, 'mongo', return_value=mongomock.MongoClient())
def test_db1(mocked_mongo):
    mongo_stuff.mongo()
    assert True


@patch.object(mongo_stuff, 'mongo', return_value=mongomock.MongoClient())
def test_db2(mocked_mongo):
    somewhere_else.foo()
    assert True

您应该避免在可能的情况下在模块级别运行代码,或者在测试内部运行在模块级别执行代码的导入(如您在注释中已经发现的那样)。