Search code examples
pythonmagicmock

Testing methods are called on object created


I'm new to Python so forgive me if this is basic. I have a method under test and in that method, I instantiate an object and call methods on it and want to test that those are called correctly (worth pointing out that this code is pre-existing and I'm merely adding to it, with no existing tests).

Method under test

def dispatch_events(event):
    dispatcher = Dispatcher()
    dispatcher.register("TopicOne")
    dispatcher.push(event)

Expected test

# Some patch here
def test_dispatch_events(self, mock_dispatcher):
    # Given
    event = { "some_prop": "some_value" }

    # When
    Class.dispatch_events(event)

    # Then
    mock_dispatcher.register.assert_called_once_with("TopicOne")
    mock_dispatcher.push.assert_called_once_with(event)

Coming from a .NET background my immediate thought is to pass Dispatcher into dispatch_events as a parameter. Then presumably, I can pass in a MagicMock version. Or I thought that you might be able to patch the __init__ method on the Dispatcher and return a MagicMock. Before I continue with this I wanted to know whether a) it's possible and b) what's the best practice for testing this (fully accepting that writing a better method might be that best practice).


Solution

  • Make dispatcher an argument, and you don't need to patch anything.

    def dispatch_events(event, dispatcher=None):
        if dispatcher is None:
            dispatcher = Dispatcher()
        dispatcher.register("TopicOne")
        dispatcher.push(event)
    
    def test_dispatch_events(self):
        event = {"some_prop": "some_value"}
        mock_dispatcher = Mock()
        Class.dispatch_events(event, mock_dispatcher)
        mock_dispatcher.register.assert_called_once_with("TopicOne")
        mock_dispatcher.push.assert_called_once_with(event)
    

    If that's not an option, the correct thing to mock in most cases will be Dispatcher.__new__ or some.module.Dispatcher itself.

    # The exact value of 'some.module' depends on how the module that
    # defines dispatch_events gets access to Dispatcher.
    @mock.patch('some.module.Dispatcher')
    def test_dispatch_events(self, mock_dispatcher):
        event = {"some_prop": "some_value"}
        Class.dispatch_events(event)
        mock_dispatcher.register.assert_called_once_with("TopicOne")
        mock_dispatcher.push.assert_called_once_with(event)