4

I have a module with a dictionary as associative array to implement a kind-of switch statement.

def my_method1():
    return "method 1"


def my_method2():
    return "method 2"


map_func = {
    '0': my_method1,
    '1': my_method2
}

def disptach(arg):
  return map_func[arg]()

How can I mock my_method1 in tests? I've tried the following without success:

import my_module as app

@patch('my_module.my_method1')
def test_mocking_sample(self, my_mock):
    my_mock.return_value = 'mocked'
    assert_equal('mocked',app.dispatch('0'))

Any idea?

epc
  • 194
  • 4
  • 12
  • 1
    I suppose you have to mock dispatch('0') instead of my_method1. That is mock dispatch when called with arg '0'. – jgomo3 Aug 31 '15 at 16:26

2 Answers2

1

This piece of patch documentation says the following:

patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

Basically, your dispatcher won't see it, as the mapping is built to reference the original method, before the patch is applied.

The simplest thing you can do to make it mockable is to fold the mapping into the dispatch function:

def dispatch(arg):
  return {
    '0': my_method1,
    '1': my_method2
  }[arg]()

This does have the downside that it rebuilds that mapping every time you call it, so it will be slower.

Trying to get a bit clever, it seems that Python lets you swap out the actual code of a function, like so:

>>> f = lambda: "foo"
>>> a = f
>>> g = lambda: "bar"
>>> f.func_code = g.func_code
>>> a()
'bar'

I won't recommend that you do it this way, but maybe you can find a mocking framework that supports something similar.

Vlad
  • 18,195
  • 4
  • 41
  • 71
1

As you've discovered, patching my_Method1() does not work. This is because map_func['0'] was defined when my_module was imported and subsequent changes to my_Method1() do not update map_func for your test. Instead, we need to patch the value in dictionary map_func for key '0' directly. The unittest.mock documentation explains how to patch a dictionary entry. Below is a working implementation of your test:

""" test_my_module.py
"""
import unittest
import unittest.mock as mock
import my_module as app


my_mock = mock.MagicMock()


class Test_mock_sample(unittest.TestCase):

    @mock.patch.dict('my_module.map_func', {'0': my_mock})
    def test_mocking_sample(self):
        my_mock.return_value = 'mocked'
        self.assertEqual('mocked', app.dispatch('0'))

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

After changing disptach to dispatch in your original my_module...

""" my_module.py
"""
def my_method1():
    return "method 1"


def my_method2():
    return "method 2"


map_func = {
    '0': my_method1,
    '1': my_method2
}

def dispatch(arg):
  return map_func[arg]()

Then the command python -m unittest test_my_module gives the following output:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

It worked!

amath
  • 1,279
  • 9
  • 14