4

I'm trying to mock a function for a test that eventually passes the mock through the inspect.getargspec function. I'd like to use some specific names as argument names of the mocked function.

As far as I know, there's no specific way to mock argument names, so I'm trying to use Mock's spec= argument:

In [1]: import mock, inspect

In [2]: inspect.getargspec(mock.Mock(spec=lambda a,b:None))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-de358c5a968d> in <module>()
----> 1 inspect.getargspec(mock.Mock(spec=lambda a,b:None))

/usr/lib/python2.7/inspect.pyc in getargspec(func)
    815     if not isfunction(func):
    816         raise TypeError('{!r} is not a Python function'.format(func))
--> 817     args, varargs, varkw = getargs(func.func_code)
    818     return ArgSpec(args, varargs, varkw, func.func_defaults)
    819 

/usr/lib/python2.7/inspect.pyc in getargs(co)
    750 
    751     if not iscode(co):
--> 752         raise TypeError('{!r} is not a code object'.format(co))
    753 
    754     nargs = co.co_argcount

TypeError: <Mock name='mock.func_code' id='140395908951120'> is not a code object

So, yes, indeed, Mock is not a code object. How do I mock argument names so that inspect.getargspec returns these names?

liori
  • 40,917
  • 13
  • 78
  • 105
  • 1
    One point of a `Mock` object is that it is often easier to create than writing a proper mock class or mock function. If the `Mock` class doesn't simplify things for you, simply write a mock function instead. (I know you won't get the function call tracking functionality for free, but maybe you don't even need it.) – Sven Marnach Aug 06 '15 at 14:30
  • @SvenMarnach: Yeah, actually I wanted to use Mock because of call tracking. But maybe you're right… – liori Aug 06 '15 at 14:31
  • 1
    You could call a mock from within your mock function for tracking. :) – Sven Marnach Aug 06 '15 at 14:33
  • @SvenMarnach: could you put both of these into an answer, so that I can properly upvote you? – liori Aug 06 '15 at 19:34
  • I've elaborated the comments in a proper answer. – Sven Marnach Aug 07 '15 at 12:14

3 Answers3

2

You could patch inspect.getargspec instead:

getargspec_orig = inspect.getargspec
def getargspec_patch(f):
    if f == <your function>:
        return ArgSpec(args=[...], varargs=..., keywords=..., defaults=(...))
    else:
        return getargspec_orig(f)

with patch('inspect.getargspec', new_callable=getargspec_patch):
    # do your testing here
bagrat
  • 7,158
  • 6
  • 29
  • 47
1

An easy solution is to use a proper mock function instead of a mock.Mock instance. If you need the call tracking of Mock instances and the implicit creation of Mock instances as return values, you can simply call such an instance from within your mock function:

def test_something(self):
    mock_instance = mock.Mock()
    def mock_callback(a, b):
        return mock_instance(a, b)
    result = function_to_test(mock_callback)
    self.assertEqual(mock_instance.call_count, 3)

If you need the callback in several functions (or don't want it to be a local function for a different reason), you can write a factory function that returns both the callback and the Mock instance:

def make_mock_callback():
    mock_instance = mock.Mock()
    def mock_callback(a, b):
        return mock_instance(a, b)
    return mock_instance, mock_callback

By closing over mock_instance, you make sure each callback gets its own Mock without polluting the prototype of the function.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
1

Managed to find a workaround that almost doesn't hurt. So, following the example from the question text:

In [1]: import mock, inspect

Define the spec function (assigned to an identifier because I'll need it twice later):

In [2]: specfun = lambda a,b: None

Define the mock. After doing some experiments I noticed that inspect.getargspec simply needs a func_code object:

In [3]: mockfun = mock.Mock(spec=specfun, func_code=specfun.func_code)

Does it work with inspect.getargspec? Yes!

In [4]: inspect.getargspec(mockfun)
Out[4]: ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=<Mock name='mock.func_defaults' id='140160757665168'>)

Does it work like a normal callable Mock? Yes!

In [5]: mockfun('hello', 42)
Out[5]: <Mock name='mock()' id='140160757483984'>

In [6]: mockfun.call_args_list
Out[6]: [call('hello', 42)]
liori
  • 40,917
  • 13
  • 78
  • 105