Alex
Alex

Reputation: 277

Mocking members of immutable classes in Python

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')

Upvotes: 1

Views: 1346

Answers (2)

Mauro Baraldi
Mauro Baraldi

Reputation: 6575

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

Upvotes: 2

Michele d'Amico
Michele d'Amico

Reputation: 23711

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

Upvotes: 0

Related Questions