简体   繁体   中英

Unittest.mock - how to mock a call to cursor.execute() from connection object?

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?

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM