How to mock MongoClient for python unit test? - python

I have following piece of code to UT, which makes me in trouble:
def initialize():
try :
self.client = MongoClient("127.0.0.1", 27017)
self.conn = self.client["DB_NAME"]
except Exception:
print "Except in initialize!"
return False
return True
I write following test case to cover the above function, hope to get return value "True":
def mock_mongodb_mongoclient_init(self, para1, para2):
pass
def mock_mongodb_mongoclient_getitem(self, name):
return {"DB_NAME":"Something"}
def test_mongodb_initialize_true(self):
self.patcher1 = patch('pymongo.MongoClient.__init__', new=self.mock_mongodb_mongoclient_init)
self.patcher2 = patch('pymongo.MongoClient.__getitem__', new=self.mock_mongodb_mongoclient_getitem)
self.patcher1.start()
self.patcher2.start()
self.assertEqual(initialize(), True)
self.patcher1.stop()
self.patcher2.stop()
But this never works! It always report "Exception in initialize!"! and return "False".
How could I UT this MongoClient and makes the function return "True"?

Since you are unit testing and not trying to actually connect to Mongo in any way, I think you should just care that the client API has been called. So I would suggest the following -
from unittest import mock
#mock.patch("pymongo.MongoClient")
def test_mongodb_initialize(self, mock_pymongo):
MyMongo.initialize()
self.assertTrue(mock_pymongo.called)
(Forgive me if my syntax is off, I use pytest rather than unittest.)

For simply pass the initialize(), we can mock the pymongo.MongoClient as following:
import unittest
import pymongo
from mock import patch
class MyMongo(object):
def initialize(self):
try :
self.client = pymongo.MongoClient("127.0.0.1", 27017)
self.conn = self.client["DB_NAME"]
except Exception:
print "Except in initialize!"
return False
return True
class TestMyMongo(unittest.TestCase):
def test_mongodb_initialize_true(self):
with patch('pymongo.MongoClient') as mock_mongo:
self.mymongo = MyMongo()
self.assertEqual(self.mymongo.initialize(), True)
However I'm not sure if you're trying to mock the MongoClient or just the MongoClient.init part?

MongoClient is not designed to be mocked this way. It must initialize its attributes in __init__ in order to function, so if you skip calling __init__, all further operations will throw various exceptions. In your specific case, MongoClient needs access to the __slave_okay attribute, but it isn't set.
Either set up an actual MongoDB server and test against it, or mock all of PyMongo with a fake library. Simply overriding a handful of methods in PyMongo is not going to work with reasonable effort.

Related

Is there a way to mock a complete bit of code in pytest using mock?

For instance, every time a test finds
database.db.session.using_bind("reader")
I want to remove the using_bind("reader")) and just work with
database.db.session
using mocker
Tried to use it like this in conftest.py
#pytest.fixture(scope='function')
def session(mocker):
mocker.patch('store.database.db.session.using_bind', return_value=_db.db.session)
But nothing has worked so far.
Code under test:
from store import database
results = database.db.session.using_bind("reader").query(database.Order.id).join(database.Shop).filter(database.Shop.deleted == False).all(),
and I get
AttributeError: 'scoped_session' object has no attribute 'using_bind' as an error.
Let's start with an MRE where the code under test uses a fake database:
from unittest.mock import Mock, patch
class Session:
def using_bind(self, bind):
raise NotImplementedError(f"Can't bind {bind}")
def query(self):
return "success!"
database = Mock()
database.db.session = Session()
def code_under_test():
return database.db.session.using_bind("reader").query()
def test():
assert code_under_test() == "success!"
Running this test fails with:
E NotImplementedError: Can't bind reader
So we want to mock session.using_bind in code_under_test so that it returns session -- that will make our test pass.
We do that using patch, like so:
#patch("test.database.db.session.using_bind")
def test(mock_bind):
mock_bind.return_value = database.db.session
assert code_under_test() == "success!"
Note that my code is in a file called test.py, so my patch call applies to the test module -- you will need to adjust this to point to the module under test in your own code.
Note also that I need to set up my mock before calling the code under test.

How to mock subsequent function calls in python?

I'm new to testing and testing in python. I have a python class that looks like this :
File name : my_hive.py
from pyhive import hive
class Hive:
def __init__(self, hive_ip):
self.cursor = hive.connect(hive_ip).cursor()
def execute(self, command):
self.cursor.execute(command)
I want to mock these functions : pyhive.hive.connect, pyhive.Connection.cursor(used by my class as hive.connect(hive_ip).cursor()) and pyhive.Cursor.execute (used by my class as self.cursor.execute(command) in execute method).
I'm able to mock function call hive.connect and also I have been able to assert that it has been called with hive_ip given by me as follows.
import unittest
import mock
from my_hive import Hive
class TestHive(unittest.TestCase):
#mock.patch('pyhive.hive.connect')
def test_workflow(self, mock_connect):
hive_ip = "localhost"
processor = Hive(hive_ip)
mock_connect.assert_called_with(hive_ip)
But how do I make sure that subsequent function calls like .cursor() and self.cursor.execute() have also been called? hive.connect(hive_ip) returns an instance of pyhive.hive.Connection, which has method called cursor
I have tried to add mocks like this :
import unittest
import mock
from hive_schema_processor import HiveSchemaProcessor
class TestHive(unittest.TestCase):
#mock.patch('pyhive.hive.connect')
#mock.patch('pyhive.hive.Connection.cursor')
def test_workflow(self, mock_connect, mock_cursor):
hive_ip = "localhost"
processor = Hive(hive_ip)
mock_connect.assert_called_with(hive_ip)
mock_cursor.assert_called()
But the tests are failed with complain :
AssertionError: expected call not found.
Expected: cursor('localhost')
Actual: not called.
Your problem is that you have already mocked connect, so the subsequent calls on the result of connect will be made on the mock, not on the real object.
To check that call, you have to make the check on the returned mock object instead:
class TestHive(unittest.TestCase):
#mock.patch('pyhive.hive.connect')
def test_workflow(self, mock_connect):
hive_ip = "localhost"
processor = Hive(hive_ip)
mock_connect.assert_called_with(hive_ip)
mock_cursor = mock_connect.return_value.cursor
mock_cursor.assert_called()
Each call on a mock produces another mock object.
mock_connect.return_value gives you the mock that is returned by calling mock_connect, and mock_connect.return_value.cursor contains another mock that will actually be called.

How can i patch out a parameter from a function that calls a method?

Hello I am pretty new to unit testing and therefore I got a big issue with unittest.mock.
My Project consists of different modules.
My first module is General:
general.py
def do_something(item, collection):
# some more code that i want to test
try:
collection.insert_one(item)
except:
print("did not work")
My second module ist my_module.py
mymodule.py
import general
from pymongo import MongoClient
client = MongoClient("localhost", 27017)
db = client['db']
collection = db['col']
item =
{
"id": 1
}
def method:
general.do_something(item, collection)
Now I want to test the do_something(item, collection) method from general.py, and therefore I want to mock the collection.insert_one(item). I did not find a possible solution for this.
I tried it with patch, but my Problem is, that the parameter collection (which is a pymongo Collection) is a parameter that calls a function. How can i now manage to mock collection.insert_one?
My target is to extract collection.insert_one and set a MagicMock into it. And this Magic Mock should have the possibility to crash to check if the except part works or to not crash to check if the try part workes.
TestGeneral.py
import unnittest
class TestGeneral(unittest.TestCase):
#patch()
def test_general():
Thank you in advance! :)
You don't actually need a mock here, you could just create a class that has a similar functionality.
TestGeneral.py
import unnittest
class TestGeneral(unittest.TestCase):
def test_general_success():
assert general.do_something(collection(), None)
def test_general_failure():
with self.assertRaises(Exception):
general.do_something(collection(fail=True), None))
class collection:
def __init__(self, fail=False):
self.fail = fail
def insert_one(self, item):
if self.fail:
raise Exception
return True
Then you can also check the stdout to make sure a print is used on success
If you had to use mock for some reason, this method could work
TestGeneral.py
import unnittest
class TestGeneral(unittest.TestCase):
#mock.patch('{your path}.my_module.MongoClient')
def test_general_success(mock_mongo):
mock_mongo.return_value = {'db': None, 'col': collection()}
assert general.do_something(None, None) # None here since the collection is mocked anyway
#mock.patch('{your path}.my_module.MongoClient')
def test_general_failure(mock_mongo):
mock_mongo.return_value = {'db': None, 'col': collection(fail=True)}
with self.assertRaises(Exception):
general.do_something(None, None))
class collection:
def __init__(self, fail=False):
self.fail = fail
def insert_one(self, item):
if self.fail:
raise Exception
return True
The downside to this is that you have now mocked the entire MongoDB, meaning any other uses of it (i.e client) will also be mocked

How do you test code that is run Immediately?

Using Python's Unittest framework how do you mock or replace a module that has code that is run as the module is loaded?
I understand this is poorly written code, but this is similar to what I have to test. (See example)
I understand that once a module is imported it can be patched to use mocks. But what if there is code that is run immediately?
I have a file that I need to put under test. One of the files it imports runs code immediately, see example.
file_under_test.py
from somewhere.something.worker import a_func as f
class ClassToTest():
__init__(self):
...
the somewhere.something.worker module
import os
import redis
REDIS_HOST = os.environ.get('redishost', '') #<-- Mock this
connection = redis.Redis(host=REDIS_HOST) #<--- Mock this
class AClass():
...
def a_func():
connection.doSomething()
...
Defer creating the connection until you are really ready for it to happen. As a bonus, you can have init_connection take an optional pre-allocated connection rather than always creating it on-demand. This makes it easier to migrate towards avoiding the global connection altogether.
import os
import redis
connection = None
def init_connection(c=None):
global connection
if connection is None:
if c is None:
c = redis.Redis(host=os.environ.get('redishost', ''))
connection = c
...
Then, in your test module, you can call init_connection from inside setupModule, with the option of passing in the desired connection-like object
instead of having to patch anything.
def setupModule():
init_connection()
# or
# conn = Mock()
# ... configure the mock ...
# init_connection(conn)
class ClassToTest():
__init__(self):
...

How I can mock mongodb find when a variable hold the mongoclient

I am trying to mock the return of db.collection.find to avoid database calls. I tried to create a mock.patch for mongoclient() and attached on it a return_value. But when my_call() call db.collection.find it just return a Mock Object. Somebody have a idea how mock it?
#dao.py
class MyDao():
def my_call():
db = mongoclient().db_name
result = db.collection.find()
return result
#test_dao.py
import dao
def test_my_call():
result = dao.my_call()
assert result == list()
I think it would help to see your current mock to edit. In general, this is how I would mock the pymongo collection.find() as a standalone function.
test_dao.py
import unittest
import mock
from mock import Mock
import pymongo
class Test_Dao(unittest.TestCase):
"""
Set up the mock
"""
#classmethod
#mock.patch('pymongo.collection')
def setUpClass(self, mock_mongo):
a = Mock()
a.find.side_effect = self.findResponse # Assign side effect for mocked method
mock_mongo.return_value = a # Importing pymongo.collection returns the mock
"""
Response Data
"""
findResponse = 'some data'
def test_my_call():
result = dao.my_call()
assert result == list()
...
In your case that is worth a try but might not work because you are calling collection.find() from a variable.
You may need to mock the MongoClient() such that db_name has a side_effect to return a fake class. Then the fake class would need a collection.find() method you define. That would be a little longer and look a little bit like this:
class FakeCollection:
def find:
return 'data'
class FakeDatabase:
def collection:
return FakeCollection()
...
#classmethod
#mock.patch('pymongo.MongoClient')
def setUpClass(self, mock_mongo):
a = Mock()
a.db_name.side_effect = self.dbResponse
mock_mongo.return_value = a
dbResponse = FakeDatabase()

Categories

Resources