Reputation:
I have a class:
class DatabaseThing():
def __init__(self, dbName, user, password):
self.connection = ibm_db_dbi.connect(dbName, user, password)
I want to test this class but with a test database. So in my test class I am doing something like this:
import sqlite3 as lite
import unittest
from DatabaseThing import *
class DatabaseThingTestCase(unittest.TestCase):
def setUp(self):
self.connection = lite.connect(":memory:")
self.cur = self.connection.cursor()
self.cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
INSERT INTO APPLE VALUES(16,0);
INSERT INTO APPLE VALUES(17,5);
INSERT INTO APPLE VALUES(18,1);
INSERT INTO APPLE VALUES(19,15);
INSERT INTO APPLE VALUES(20,20);
INSERT INTO APPLE VALUES(21,25);''')
How would I go about using this connection than the connection from the class I want to test? Meaning using the connection from setUp(self)
instead of the connection from DatabaseThing
. I cannot test the functions without instantiating the class. I want to mock the __init__
method somehow in the Test Class, but I didn't find anything that seemed useful in the documentation.
Upvotes: 45
Views: 68948
Reputation: 13054
A long term solution is to have the client create the connection and hand it over to DatabaseThing
to use. Using the Single Responsibility principle, I don't think DatabaseThing
should be responsible for making the connection in this case.
This technique cuts dependencies and gives you a lot more flexibility e.g. you can setup a connection pool and give each new instance of DatabaseThing
a connection from the pool, without changing anything in DatabaseThing
.
Upvotes: 5
Reputation: 1436
If you want to return a mock when you initialize a class, mock out the__new__ method, not init.
new makes the new instance and init initializes it, but can only return None.
If you mock new, it can return a mock you can assert on to simulate instance creation in the test.
@mock.patch('Car.__new__')
def test_create_car(self, mock_Car):
mock_inst = mock.MagickMock()
mock_Car.return_value = mock_inst
create_car()
# Assert class was called
mock_Car.assert_called_once()
# __new__ is called with actual class as first arg
mock_Car.assert_called_with(Car)
# Assert instance method is called as expected
mock_inst.set_miles.assert_called_with(0)
Upvotes: 31
Reputation: 10639
You should use mock
package to mock the method __init__
of the class:
from mock import patch
def test_database_thing(self):
def __init__(self, dbName, user, password):
# do something else
with patch.object(DatabaseThing, '__init__', __init__):
# assert something
Upvotes: 17
Reputation: 15854
Considering ibm_db_dbi
and lite
share the same interfaces, this should do the trick:
import mock
import sqlite3 as lite
class DatabaseThingTestCase(unittest.TestCase):
def setUp(self):
self.patch = mock.patch('module_with_DatabaseThing_definition.ibm_db_dbi', lite)
self.patch.start()
def tearDown(self):
self.patch.stop()
I.e. your DatabaseThing
file is named database/things.py
then the patch would look like this database.things.ibm_db_dbi
.
Example of the mocking:
moduleA.py
def connection(*args):
print 'The original connection. My args', args
moduleB.py
def connection(*args):
print 'The mocked connection. My args', args
myClass.py
import moduleA
class MyClass(object):
def __init__(self):
self.connection = moduleA.connection('Test', 'Connection')
test.py
import mock
import moduleB
from myClass import MyClass
def regular_call():
MyClass()
def mocked_call():
def wrapped_connection(*args):
return moduleB.connection(':memory:')
my_mock = mock.Mock(wraps=moduleB)
my_mock.connection = wrapped_connection
with mock.patch('myClass.moduleA', my_mock):
MyClass()
MyClass()
regular_call()
mocked_call()
Running test.py gives:
The original connection. My args ('Test', 'Connection')
The mocked connection. My args (':memory:',)
The original connection. My args ('Test', 'Connection')
Upvotes: 1
Reputation: 1121804
Instead of mocking, you could simply subclass the database class and test against that:
class TestingDatabaseThing(DatabaseThing):
def __init__(self, connection):
self.connection = connection
and instantiate that class instead of DatabaseThing
for your tests. The methods are still the same, the behaviour will still be the same, but now all methods using self.connection
use your test-supplied connection instead.
Upvotes: 77
Reputation: 2845
Rather than try to replace the init function which is messy, fragile and hacky, try passing a function to your database constructor as shown below:
# Test connection creation
def connect_lite(dbName=None, user=None, password=None):
connection = lite.connect(":memory:")
cur = self.connection.cursor()
cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
INSERT INTO APPLE VALUES(16,0);
INSERT INTO APPLE VALUES(17,5);
INSERT INTO APPLE VALUES(18,1);
INSERT INTO APPLE VALUES(19,15);
INSERT INTO APPLE VALUES(20,20);
INSERT INTO APPLE VALUES(21,25);''')
return cur
# Production connection creation
def connect_ibm(dbName, user, password):
return ibm_db_dbi.connect(dbName, user, password)
# Your DatabaseThing becomes:
class DatabaseThing():
def __init__(self, connect, dbName, user, password):
self.connection = connect(dbName, user, password)
# In your test create a DatabaseThing
t = DatabaseThing(connect_lite, dbName, user, password)
# In your production code create a DatabaseThing
p = DatabaseThing(connect_ibm, dbName, user, password)
This has the side benefit of slightly decoupling your code from the database technology you are using.
Upvotes: 2