4

I am trying to stub out cursor.execute() in the following code with mock such that I can test execute is called with a correctly formatted query:

// Module ABC

def buildAndExecuteQuery( tablesAndColumnsToSelect ):
   '''Build and execute a query.

   Args: tablesAndColumnsToSelect (dict)
         - keys are table names, values are columns

   '''
   query = ..... < logic to build query > ....

   from django.db import connections
   cursor = connections[ 'mydb' ].cursor()
   cursor.execute( query )

How can I accomplish this type of mock in python2.7 with the mock library?

bwrabbit
  • 529
  • 1
  • 4
  • 25
  • [mox 0.5.3: New uses of this library are discouraged.](https://pypi.python.org/pypi/mox) Is there any reason why you have to use mox? – G_M Feb 19 '18 at 23:17
  • Not really, though it is common practice where I am working. A working solution using another mock library would be ok-- I guess even better if new uses of mox are discouraged. – bwrabbit Feb 19 '18 at 23:34
  • I hadn't heard of `mox` and that was just a quote from the pypi page itself. It also hasn't been updated since 2010. I'll try to write an example for you right now but the imports are slightly different between versions (Python 3 mock is in the stdlib but Python 2 is third-party). I'm guessing Python 2 since `mox` is Python 2 only? – G_M Feb 19 '18 at 23:38
  • 1
    Yes Python 2.7. Sure, thanks for the help Delirious Lettuce! – bwrabbit Feb 19 '18 at 23:45
  • 1
    I have updated the post to refer to mock library instead of pymox. – bwrabbit Feb 20 '18 at 00:27

2 Answers2

4

I will expand @G_M answer with example since I have two objections:

  1. In my opinion, it is a good practice to explicitly close database cursor, more about this here: Necessity of explicit cursor.close(). This can be done by using the cursor as a context manager, more in Django docs: https://docs.djangoproject.com/en/dev/topics/db/sql/#connections-and-cursors.
  2. When using mock we should not patch objects where they are defined:

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined. A couple of examples will help to clarify this.

Ref: https://docs.python.org/3/library/unittest.mock.html#where-to-patch

Example:

Our function we want to test:

# foooo_bar.py
from typing import Optional

from django.db import DEFAULT_DB_ALIAS, connections


def some_function(some_arg: str, db_alias: Optional[str] = DEFAULT_DB_ALIAS):
    with connections[db_alias].cursor() as cursor:
        cursor.execute('SOME SQL FROM %s;', [some_arg])

Test:

# test_foooo_bar.py
from unittest import mock

from django.db import DEFAULT_DB_ALIAS
from django.test import SimpleTestCase

from core.db_utils.xx import some_function


class ExampleSimpleTestCase(SimpleTestCase):
    @mock.patch('foooo_bar.connections')
    def test_some_function_executes_some_sql(self, mock_connections):
        mock_cursor = mock_connections.__getitem__(DEFAULT_DB_ALIAS).cursor.return_value.__enter__.return_value
        some_function('fooo')

        # Demonstrating assert_* options:
        mock_cursor.execute.assert_called_once()
        mock_cursor.execute.assert_called()
        mock_cursor.execute.assert_called_once_with('SOME SQL FROM %s;', ['fooo'])
illagrenan
  • 6,033
  • 2
  • 54
  • 66
2

Since I don't know what your query logic is, I modified the query to just accept a sentinel value directly through the tables_and_columns_to_select argument.

# b_and_ex_q.py


def build_and_execute_query(tables_and_columns_to_select):
    """Build and execute a query.

    Args: tablesAndColumnsToSelect (dict)
          - keys are table names, values are columns

    """

    query = tables_and_columns_to_select  # ..... < logic to build query > ....

    from django.db import connections

    cursor = connections['mydb'].cursor()
    cursor.execute(query)

import unittest

from mock import patch, sentinel

from b_and_ex_q import build_and_execute_query


class TestCursorExecute(unittest.TestCase):
    @patch('django.db.connections')
    def test_build_and_execute_query(self, mock_connections):
        # Arrange
        mock_cursor = mock_connections.__getitem__('mydb').cursor.return_value

        # Act
        build_and_execute_query(sentinel.t_and_c)

        # Assert
        mock_cursor.execute.assert_called_once_with(sentinel.t_and_c)


if __name__ == '__main__':
    unittest.main()
G_M
  • 3,342
  • 1
  • 9
  • 23
  • 1
    Thanks Delirious Lettuce, this was confusing me all day. Btw I found that the patch to apply for a local "from django.db import connections" import inside of buildAndExecuteQuery() is simply: @patch( 'django.db.connections' ). – bwrabbit Feb 20 '18 at 01:26
  • @bwrabbit No problem, I also updated the patch as per your comment. It's nice to see a fellow Vancouverite on here! – G_M Feb 20 '18 at 01:33