Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strict mock in python

Is there any equivalent of strict mocks in python? Some mechanism to report unintended call of mocked methods (action.step2() in this example), just like this in GoogleMock framework.

class Action:
    def step1(self, arg):
        return False

    def step2(self, arg):
        return False

def algorithm(action):
    action.step1('111')
    action.step2('222')
    return True

class TestAlgorithm(unittest.TestCase):
    def test_algorithm(self):
        actionMock = mock.create_autospec(Action)
        self.assertTrue(algorithm(actionMock))
        actionMock.step1.assert_called_once_with('111')
like image 846
NiegodziwyBeru Avatar asked Jul 08 '14 11:07

NiegodziwyBeru


1 Answers

Looks like it's not supported out of the box. However there are at least two approaches on how to achieve the same result.

Passing list of allowed members

According to mock documentation

spec: This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError.

So, in order to fail your test example just replace

actionMock = mock.create_autospec(Action)

to

actionMock = mock.Mock(spec=['step1'])

Such an approach have certain drawbacks compared to passing class or instance as spec argument, as you have to pass all the allowed methods and than set up expectations on them, effectively registering them twice. Also, if you need to restrict a subset of methods you have to pass list of all methods execept those. This can be achieved as follows:

all_members = dir(Action)  # according to docs this is what's happening behind the scenes
all_members.remove('step2')  # remove all unwanted methods 
actionMock = mock.Mock(spec=all_members)

Setting exceptions on restricted methods

Alternative approach would be to excplicitly set failures on methods you don't want to be called:

def test_algorithm(self):
    actionMock = mock.create_autospec(Action)
    actionMock.step2.side_effect = AttributeError("Called step2") # <<< like this
    self.assertTrue(algorithm(actionMock))
    actionMock.step1.assert_called_once_with('111')

This have some limitations as well: you've got to set errors as well as expectations.

As a final note, one radical solution to the problem would be to patch mock to add strict parameter to Mock constructor and send a pull request. Than either it would be accepted or mock maintainers will point out on how to achieve that. :)

like image 195
J0HN Avatar answered Sep 24 '22 06:09

J0HN