Search code examples
pythonmockingpython-unittest

Why is my assert_called_with returning AssertionError?


I am writing some tests, using @patch. Seemingly the patch works, and the object I have patched should be called several times with multiple arguments, but it doesn't and I am not sure why.

class testMysqlBase(mySQLTestCase):
    def setUp(self):
        super().setUp()

    def tearDown(self) -> None:
        return super().tearDown()

    @patch("mysql.connector.connection.MySQLConnection")
    def test_exception_create_db_called_if_no_db(self, mysql_cnx) -> None:
        mysql_db_ex = MysqlDBExecutor(mysql_cnx, "test_db")
        mysql_cnx.cursor.return_value.__enter__.return_value = mysql_cnx.cursor

        mysql_cnx.cursor.return_value.__enter__.return_value.execute.side_effect = [
            (mysql.connector.errors.DatabaseError(errno=errorcode.ER_BAD_DB_ERROR)),
            None,
            None,
            None,
        
            mysql_db_ex.execute("test")
            mysql_cnx.cursor.execute.assert_called_with(
               ["USE test_db;",
               "CREATE DATABASE IF NOT EXISTS test_db",
              "USE test_db;",
               "test"]
        )


from re import I

import mysql.connector
from mysql.connector import errorcode


class MysqlDBExecutor:

    logger: Logger = getLogger(__name__)

    def __init__(self, cnx: mysql.connector.connection, db: str):
        self.cnx = cnx
        self.db = db

    def execute(self, cmd) -> None:

        with self.cnx.cursor() as mycursor:
            try:
                mycursor.execute("USE " + self.db + ";")
            except mysql.connector.Error as err:
                if err.errno == errorcode.ER_BAD_DB_ERROR:
                    self.create_mysql_db()
                    mycursor.execute("USE " + self.db + ";")
                else:
                    raise err(
                        f"Exception thrown in mysqlDBExecutor.execute on {self.db}"
                    )

            try:           
                mycursor.execute(cmd)
            except mysql.connector.Error as err:
                raise err(f"Exception thrown in mysqlDBExecutor.execute on {self.db}")

    def create_mysql_db(self) -> None:

        mysql_query = "CREATE DATABASE IF NOT EXISTS {}".format(self.db)

        with self.cnx.cursor() as mycursor:
            try:
                mycursor.execute(mysql_query)
            except mysql.connector.Error as err:
                raise err(
                    f"mysql Exception thrown in mysqlExecutor creating db{self.db}"
                )
            except Exception:

                raise

So in the test, when I call mysql_db_ex.execute on the database which doesn't exist the patched mysql_cnx cursor has a DatabaseError side_effect the first time round, which gets caught, calling self.create_mysql_db()

In there the cursor is called again to create the database, then the database is used and finally the command cmd should be issued. So it should get called 4 times, but i get;

E           AssertionError: expected call not found.
E           Expected: execute(['USE test_db;', 'CREATE DATABASE IF NOT EXISTS test_db', 'USE test_db;', 'test'])
E           Actual: execute('test')

Can anyone explain where I am going wrong? I know the mysql_cnx.cursor.execute gets called 4 times, with those argyuments - so I am a bit confused.


Solution

  • https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with

    This method is a convenient way of asserting that the last call has been made in a particular way

    assert_called_with only checks the last call made, you are trying to use it to do 4 separate call checks.

    You probably want to use https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls