0

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

ediblecode
  • 11,701
  • 19
  • 68
  • 116

1 Answers1

1

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)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Good stuff. This was my first thought, but then I didn’t know whether I should be like: that allows me to test but then I can’t test that if it is ‘None’ that it actually creates a ‘Dispatcher’? Made me think there might be another way? – ediblecode Mar 19 '18 at 17:34
  • 1
    One extreme would be to patch the name `Dispatcher`; the other would be to make the `dispatcher` non-optional, so that `dispatch_events` is no longer responsible *at all* for creating the `Dispatcher` instance. – chepner Mar 19 '18 at 17:47
  • I think at the end of the day, this allows me to test and is better than the alternative. Out of interest could you illustrate the first option? – ediblecode Mar 19 '18 at 17:49
  • 1
    Another option is to make `dispatch_events` a method of `Dispatcher`, rather than a standalone function that creates an instance. – chepner Mar 19 '18 at 17:52