mysqlpython-3.xpython-unittestpython-unittest.mockmagicmock

Mock the fetchone() method of the Mysql Cursor class and set its return value to None


I'm trying to make a MagicMock instance of the mysql connector, but I need for the method fetchone() to return None.

This is what I've done so far:

with mock.patch('mysql.connector.cursor') as dbmock, \
       mock.patch('mysql.connector.connect', mock.MagicMock()):
        dbcursor_mock = dbmock.return_value  # Get the mock for the cursor
        dbcursor_mock.fetchone.return_value = None  # Set the return value of fetchone

The problem is that this returns a MagicMock instance and not None.

Now, if I remove the second patch(), it does work:

with mock.patch('mysql.connector.cursor') as dbmock):
        dbcursor_mock = dbmock.return_value  
        dbcursor_mock.fetchone.return_value = None  # this does return None, Why?

But then my code will try to connect to the db and fail.

I'm using the MySQL cursor within a context manager like this:

def fetch_a_row():
# establishing the connection
with mysql.connector.connect(user='root',password='password',host='127.0.0.1',database='mydb') as conn:
    # Creating a cursor object using the cursor() method
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM mytable")
    # return the fetchone() output
    return cursor.fetchone()

How can I make an instance of MagicMock return None?


Solution

  • Use of patch.object() and side_effect

    I have write a test which can be force fetchone() to returne None by the use of:

    A test adapt for your need should be the following:

    import unittest
    from unittest import mock
    
    import mysql.connector
    
    def fetch_a_row():
        # establishing the connection
        conn = mysql.connector.connect(user='root',password='password',host='127.0.0.1',database='mydb')
        # Creating a cursor object using the cursor() method
        cursor = conn.cursor()
        # return the fetchone() output
        return cursor.fetchone()
    
    class TestMysqlConn(unittest.TestCase):
    
        def test_01(self):
            # following patch() is the same you have used in your code
            with mock.patch('mysql.connector.connect') as mock_connect:
                mock_connect_instance = mock_connect.return_value
                # mock the method cursor() of the mock object mock_connect
                with mock.patch.object(mock_connect_instance, 'cursor') as mock_cursor:
                    mock_cursor_instance = mock_cursor.return_value
                    # force fetchone() to return None by side_effect
                    mock_cursor_instance.fetchone.side_effect = [None]
                    # check if fetch_a_row() return None
                    self.assertEqual(None, fetch_a_row())
    
    if __name__ == '__main__':
        unittest.main()
    

    In the code I have written the function fetch_one_row() which simulate your production code.


    EDIT: I have edited the answer because the OP have add his code for the function fetch_a_row().

    Test with the context manager

    You have edited your question and add the code of the function fetch_a_row() with the use of the context manager.
    When you use a context manager is called the method __enter__() (see this link) and this method returns the object conn. So with context manager I have to modify the test function as following:

    import unittest
    from unittest import mock
    
    import mysql.connector
    
    def fetch_a_row():
        # establishing the connection
        with mysql.connector.connect(user='root',password='password',host='127.0.0.1',database='mydb') as conn:
            # Creating a cursor object using the cursor() method
            cursor = conn.cursor()
            cursor.execute("SELECT * FROM mytable")
            # return the fetchone() output
            return cursor.fetchone()
    
    class TestMysqlConn(unittest.TestCase):
    
        def test_01(self):
            with mock.patch('mysql.connector.connect') as mock_connect:
                mock_connect_instance = mock_connect.return_value
                with mock.patch.object(mock_connect_instance, '__enter__') as mock_connect_context_manager:
                    mock_connect_context_manager_instance = mock_connect_context_manager.return_value
                    with mock.patch.object(mock_connect_context_manager_instance, 'cursor') as mock_cursor:
                        mock_cursor_instance = mock_cursor.return_value
                        mock_cursor_instance.fetchone.side_effect = [None]
                        self.assertEqual(None, fetch_a_row())
    
    if __name__ == '__main__':
        unittest.main()