У меня есть код, который подключается к клиенту MongoDB, и я пытаюсь его протестировать. Для тестирования я не хочу подключаться к фактическому клиенту, поэтому я пытаюсь найти подделку для тестирования. Основной поток кода: у меня есть функция где-то, создает клиент pymongo
, затем запрашивает это и делает dict, который используется в другом месте.
Я хочу написать несколько тестов с помощью 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
somewhere_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()
И это в настоящее время дает мне ошибку подключения, поскольку параметры connection params
в файле mongo_stuff.py предназначены только для работы в рабочей среде. Если я поставлю оператор import
из test_stuff.py в тестовую функцию, то он отлично работает, а mongomock
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
запускает код подключения также на уровне модуля. Поэтому исправление с помощью светильников будет слишком поздно и не будет иметь никакого эффекта. Вы должны пропатчить клиент Монго перед импортом 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
Вам лучше избегать запуска кода на уровне модуля, если это возможно, или запустить импорт, который выполняет код на уровне модуля внутри тестов (как вы уже узнали в комментариях).