Dinesh
Dinesh

Reputation: 1245

Is there a way to mock an instance variable when imported using "from .." in python?

New to Python pytest, below are the code snippets which I am using to mock a MongoDB connection.

My Mongo Connection util, which is used by my all dao layers:

connection_util.py

from pymongo import MongoClient

mongo_connection = MongoClient()

This is one of my DAO layers, used to get student information:

student_dao.py

from connection_util import mongo_connection

class StudentDAO:

    def get_student_info(self, student_id):
        student_table = mongo_connection['test_db']['student']
        student_data = student_table.find_one({"_id": student_id})
        return student_data

My pytest conftest file which contains the mongomock connection fixture:

conftest.py

import mongomock
import pytest

@pytest.fixture(scope='class', autouse=True)
def patch_mongo():
    mongo_connection = mongomock.MongoClient()
    
    yield mongo_connection
    
    mongo_connection.drop_database('mongodb')
    mongo_connection.drop_database('testdb')
    mongo_connection.close()

My test file. Here I am trying to mock the actual mongo_connection instance with the Mongomock instance:

test_student.py

import connection_util
from student_dao import StudentDAO

@pytest.mark.usefixtures("patch_mongo")
class TestStudent:

    def test_student(self, patch_mongo):
        with patch.object(connection_util, "mongo_connection", patch_mongo):
            student_id = "123546"
            student = StudentDAO()
            student_data = student.get_student_info("123546")
            assert student_id == student_data.get("_id")

In the patch.object my target is connection_util, my attribute is mongo_connection and my new variable is patch_mongo(pytest fixture). It successfully mocks my mongo_connection variable to pymongo MongoClient and it works within the with statement. But in my studentdao.py it still refers to the pymongo MongoClient instead of mongomock MongoClient.

When I change the import statement of connection_util in student_dao.py from "from connection_util import mongo_connection" to "import connection_util" and change the "mongo_connection" to "connection_util.mongo_connection" it is working correctly. Below code works. It replaces the pymongo MongoClient to mongomock MongoClient.

import connection_util

class StudentDAO:

    def get_student_info(self, student_id):
        student_table = connection_util.mongo_connection['test_db']['student']
        student_data = student_table.find_one({"_id": student_id})
        return student_data

The issue here is I cannot do these changes to all my DAO layers as there are many files and doing it manually takes more time too. Is there a way to mock the mongo_connection instance without replacing the import statement?

Upvotes: 0

Views: 597

Answers (1)

MrBean Bremen
MrBean Bremen

Reputation: 16855

The main problem in your test is that you patch the wrong object. This is documented in the unittest documentation under the section where to patch.
It means that if you use from a import b in module module you have to patch the reference of b in module, e.g. patch module.b instead of a.b. If you use patch.object, that means using patch.object(module, "b") instead of patch.object(a, "b").

In your case you should use something like:


from patch_mongo import student_dao

class TestStudent:

    def test_student(self, patch_mongo):
        with patch.object(student_dao, "mongo_connection", patch_mongo):
            student = student_dao.StudentDAO()
            ...

Note that I also changed the import for StudentDAO for a similar reason. With your code, you would have created another reference of StudentDAO in your test module, which has its own reference of mongo_connection.

Also note that your test as is does not work (you never set the student data), but I guess this is due to dumbing down the code.

Some other helpful links:

As an aside: This has been mentioned in many SO answers (including my own), and I'm not usually adding another answer. The problem with these question is the summary, that will not be found by anyone searching for a similar issue. Your question summary, however, really shows the problem, and hopefully will be found easier...

Upvotes: 0

Related Questions