How do I set up and verify Python mock for chained calls?

16 views Asked by At

Say I want to test my calls Greeter which depends a 3rd party class Foo which in turn depends on another class Bar. I need to mock Foo, but how do I set up and verify the chained call self.foo.get_bar().format(name)?

greeter.py:

class Bar:

  def format(self, name):
    return name.upper()


class Foo:

  def __init__(self):
    self.bar = Bar()

  def get_bar():
    return self.bar


class Greeter:

  def __init__(self, foo):
    self.foo = foo

  def hi(self, name):
    return f'Hi {self.foo.get_bar().format(name)}'

PS: this question is purely about the use of Python mock, not about the best practice of writing Python code and test, so I won't refactor the code.

1

There are 1 answers

0
Dagang Wei On

After some research, I found the following solution:

from unittest import mock
from unittest.mock import patch

import greeter
import unittest

class TestGreeter(unittest.TestCase):

  def setUp(self):
    self.foo_patcher = patch('greeter.Foo')
    self.foo_mock_class = self.foo_patcher.start()
    self.foo_mock = self.foo_mock_class()
    self.foo_mock.get_bar.return_value.format.return_value = 'EMY'

  def tearDown(self):
    self.foo_patcher.stop()

  def test_greeter(self):
    g = greeter.Greeter(self.foo_mock)

    result = g.hi('emy')

    self.assertEqual(result, 'Hi EMY')
    self.foo_mock.get_bar.return_value.format.assert_called_once_with('emy')
    self.foo_mock.get_bar.return_value.assert_has_calls([mock.call.format('emy')])
    self.foo_mock.assert_has_calls([mock.call.get_bar(), mock.call.get_bar().format('emy')])


if __name__ == '__main__':
    unittest.main()