Search code examples
pythonmockingpytestpytest-mockspying

Pytest - spying method calls on a dependent object


class Work:
    pass
class Engineer:

   def delegate(work: Work):
       print('Doing all the hard work')
class Manager:

   def __init__(self, engineer: Engineer):
       self.engineer = engineer

   def perform(work: Work):
       self.engineer.delegate(work)

I want to test Manager's perform() method. In doing so I want to verify if engineer.delegate() method was called with the right params:

    # pseudo 

    work = mock(Work)
    engr = mock(Engineer)

    mgr = spy(Manager)
    mgr.perform(work)

    verify(engr.delegate).times(1).capture(args)
    assert args == work
    

I can't seem to wrap my head over how the above can be accomplished with pytest and pytest-mock.


Solution

  • Since you inject your dependencies into your Manager class it is straight-forward to test. As noted in your pseudo-code we just need to create some mocks for the dependencies and then we can use spy to verify number of calls and the arguments the functions were called with.

    One thing to mention, in your Manager.perform function definition, you forgot to include self as being one of the arguments passed in.

    from src.manager import Manager, Engineer, Work
    
    
    def test_manager_delegation(mocker):
        mock_engineer = mocker.Mock(spec=Engineer)
        mock_work = mocker.Mock(spec=Work)
        manager = Manager(mock_engineer)
    
        perform_spy = mocker.spy(manager, "perform")
        delegate_spy = mocker.spy(mock_engineer, "delegate")
        manager.perform(mock_work)
    
        perform_spy.assert_called_once_with(mock_work)
        delegate_spy.assert_called_once_with(mock_work)
    

    =========================================== test session starts ===========================================
    platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
    rootdir: ***
    plugins: asyncio-0.18.3, hypothesis-6.48.1, mock-3.7.0
    asyncio: mode=strict
    collected 1 item                                                                                          
    
    tests/test_manager.py .                                                                             [100%]
    
    ============================================ 1 passed in 0.01s ============================================