Search code examples
pythonpython-3.xpython-mock

How to do assert on instantiated mock class object


In the constructor of my class under test a socket object is instantiated and assigned to a class member. I mocked the socket class and set a mocked socket object as return value to the socket constructor call. I then want to assert that connect() and sendall() is called on that object. I always get the assert error that the functions are not called when I assert on the original mocked class object or the one that I set to return on constructor call.

I know I can’t mock the class that is under test (and its members) because that would defeat the purpose here.

Pseudo code:

import socket

Class socketHandler():
    def __init__(...):
    self.mySocket = socket(...)
    ...
    self.mySocket.connect(...)

    def write(message):
        self.mySocket.sendall(message)

Test:

from unittest import mock
from unittest.mock import MagicMock #not sure if i need this
import pytest
import socketHandler

@mock.patch(socketHandler.socket)
def test_socket_handler(mockSocket):
    ...
    new_sock = mock_socket()
    mock_socket.return_value = new_sock

    mySocketHandler = SocketHandler(...)

    mock_socket.socket.assert_called_with(...)
    new_sock.connect.assert_called_with(...) #fails (never called)
    mock_socket.connect.assert_called_with(...) #fails (never called)
    #likewise for the sendall() method call when mysocketHandler.write(..)
    #is called

The purpose of this test is:

  1. ensure the constructor of socket library is called with the right arguments.

  2. ensure that connect() is called with right arguments.

  3. ensure that sendall() is called with exactly what I want it to be called, when I pass message into mySocketHandler.write() method.


Solution

  • The complete answer derived from hints given by @ryanh119 and this post link

    I will fix the example given above by ryanh119 and refrain from editing original question which i messed up, so for completeness:

    from unittest import mock
    import pytest
    import socketHandler
    
    @mock.patch("app_directory.socketHandler.socket")
    def test_socket_handler(mockSocketClass):
    
    # mockSocketClass is already a mock, so we can call production right away.
    mySocketHandler = SocketHandler(...)
    
    # Constructor of mockSocketClass was called, since the class was imported
    #like: import socket we need to:
    mockSocketClass.socket.assert_called_with(...)
    
    # Production called connect on the class instance variable
    # which is a mock so we can check it directly.
    # so lets just access the instance variable sock
    mySocketHandler.mySocket.connect.assert_called_with(...)
    
    # The same goes for the sendall call:
    mySocketHandler.mySocket.sendall.assert_called_with(expectedMessage)
    

    I also did some research and there would have been two more solutions that I want to mention. They are not as pythonicaly correct like the above ones but here it is:

    1. Make use of dependency injection by changing the __init__ of socketHandler to take in a socket object and only instantiate it if not supplied in the args. That way i could have passed in a mock or MagicMock object and used that to do the asserts on.
    2. Make use of a extremely powerful mocking/patching tool called MonkeyPatch which actually can patch/mock instance variables of classes. This approach would have been like trying to kill a fly with a rocket launcher.