1

I have a class which uses a sqlite3 database and want to write a test suite for it. In particular, I want to check that sqlite3.Cursor.execute is called with the correct SQL command. However, I have run into trouble with mocking this method since sqlite3.Cursor seems to be written in C and thus the class is immutable. This means I can't just patch the execute method, but if I try to patch the whole class, the assert fails, saying that execute was never called.

Below is my best attempt so far, but the assert fails, saying that there was no call. I would appreciate some suggestions as to what I'm doing wrong. Thanks.

myclass.py

import sqlite3
class MyClass:
    def __init__(self):
        self.db = sqlite3.connect('somedb.db')

    def query(self, sql_squery):
        c = self.db.cursor()
        c.execute(sql_query)

test_myclass.py

import unittest
import mock
import myclass

class MyClassTestCase(unittest.TestCase):
    @patch('myclass.sqlite3.Cursor')
    def test_query(self, mock_sql_cursor):
        mc = myclass.MyClass()
        mc.query('test')
        mock_sql_cursor.execute.assert_called_with('test')
Alex
  • 257
  • 3
  • 9

2 Answers2

2

Here goes another approach

You could patch out myclass.sqlite3 and mock the and then mock the return value of connect().cursor().execute.

import unittest
from mock import patch
from myclass import MyClass


class MyClassTestCase(unittest.TestCase):
    def test_query(self, mock_sql_cursor):
        with patch('uploads.myclass.sqlite3') as mocksql:
            mc = MyClass()
            mc.query('test')
            mock_sql_cursor.connect().cursor().execute.assert_called_with('test')

Answer inspired in this answer

Community
  • 1
  • 1
Mauro Baraldi
  • 6,346
  • 2
  • 32
  • 43
  • 1
    Thanks. This worked and seems to be the most concise solution. – Alex Feb 02 '16 at 21:10
  • 1
    Pay attention to use mock's call instead of `return_value` in test code. Mock `()` calls are reserved to production code and `return_value` is useful in test to have the same value without change the mock state. – Michele d'Amico Feb 02 '16 at 22:45
0

sqlite3 is a C extension and you cannot patch C calls. Any way you should not to test sqlite3 behavior but just your how code the call sqlite3 module.

What you can do is to patch sqlite3.connect() method and check if your code call the API in the correct way:

class MyClassTestCase(unittest.TestCase):
    @patch('sqlite3.connect', autospec=True)
    def test_query(self, mock_connect):
        mock_cursor = mock_connect.return_value.cursor.return_value
        mc = myclass.MyClass()
        mc.query('test')
        mock_cursor.execute.assert_called_with('test')

Note:

  1. I patch sqlite3.connect absolute path and not myclass.... do the same thing but it is more clear (sqlite3.connect and myclass.sqlite3.connect are exactly the same reference)
  2. I'm using autospec=True to avoid strange errors like explained in Autospeccing
  3. Consider to write your own wrapper and mock it in your test: your code will be more testable and less coupled to sqlite3 module
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
  • Thanks this worked great. Just one question -- why are `sqlite3` and `membership.sqlite3` the same namespace? I did also consider writing a wrapper class, but it seemed silly to add unnecessary abstraction just to fit my code to the test suite. – Alex Feb 02 '16 at 20:55
  • There's no namespace in python. By `import sqlite3` you copy the `sqlite3` module reference in your module and call it `sqlite3`. That means by `myclass.sqlite3.connect` you are pointing the same reference as `sqlite3.connect` – Michele d'Amico Feb 02 '16 at 21:12
  • Everywhere seems to stress the importance of patching something where it is used rather than where the call to patch is. Why is that necessary if all references to a module are the same? – Alex Feb 02 '16 at 21:15
  • Because you should patch the reference where you use it but not browse it. Patch the reference where you use is a good rule to make the changes locally but you should know exactly what you are doing. I love to make it explicit by use the absolute path where it is the same. – Michele d'Amico Feb 02 '16 at 21:38