43

The mocking library I use is ... mock.

I came across this "mock nested functions" problem when I tried to write test case for a function(legacy code).

This function used a very complex nested function with heavy dependencies on other modules.

I wonder if it's possible to mock nested functions with mock.

satoru
  • 31,822
  • 31
  • 91
  • 141

4 Answers4

75

for example you need to mock nested function calls (chained functions) from Google DRIVE API

result = get_drive_service().files().insert(body='body', convert=True).execute()   

so you need to patch through functions: service_mock(), files(), insert(), till last execute() response:

from mock import patch
with patch('path.to.import.get_drive_service') as service_mock:
   service_mock.return_value.files.return_value.insert.\
   return_value.execute.return_value = {'key': 'value', 'status': 200}

Main scheme: first.return_value.second.return_value.third.return_value.last.return_value = rsp

pymen
  • 5,737
  • 44
  • 35
  • 8
    This saved my weekend. Note that nested attributes doesn't need to be chained with a `return_value`. – Antwan Apr 29 '16 at 18:32
  • 3
    My issue with this approach is that it has to mock the outer service function first before you mock the inner nested function. Sometimes, we just want to mock the inner function itself. – HZhang Oct 17 '19 at 20:36
  • 3
    No need to use `return_value` for all but the last element, i.e. the following works too: `first().second().third().last.return_value = rsp` – Alex K Nov 11 '20 at 16:07
  • 1
    @AlexK, that may be true for specific conditions. I've something right in front of me that says otherwise. Between us, you could at least say "when in doubt, use `return_value` for _every single nested method_". – Cody Dec 23 '20 at 02:02
  • @Cody, any examples of when that doesn't work? – Alex K Jul 05 '21 at 08:18
  • How would one grab the values of body and convert in insert(body='body', convert=True) so return different based on the values passed in? – daniel langer Jun 08 '22 at 14:49
  • @daniellanger you can try with introducing new mock insert_mock as func which will return new mock def insert_side_effect(*arg, **kwargs): return Mock(...) service_mock.return_value.files.return_value.insert.side_effect = insert_side_effect – pymen Jun 09 '22 at 11:09
6

One option is to change your function so that it optionally accepts the function to call e.g. if you have:

def fn_to_test():
  def inner_fn():
    return 1
  return inner_fn() + 3

Change it to:

def fn_to_test( inner_fn = null )
  def inner_fn_orig():
    return 1
  if inner_fn==null:
    inner_fn = inner_fn_orig
  return fn() + 3

Then "real" uses will get the right inner function, and in your tests you can provide your own.

fn_to_test() # calls the real inner function
def my_inner_fn():
  return 3
fn_to_test( inner_fn=my_inner_fn ) # calls the new version

You could also do this:

def fn_to_test():
  def inner_fn_orign():
    return 1
  inner_fn = inner_fn_orig
  try:
    inner_fn = fn_to_test.inner_fn
  excecpt AttributeError:
    pass
  return inner_fn() + 3

This way you just define the override:

fn_to_test() # calls the real inner function
def my_inner_fn():
  return 3
fn_to_test.inner_fn = my_inner_fn
fn_to_test() # calls the new version
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • Nice job, it does work but this is actually an anti-pattern. It causes tons of bloat and other badness to create extra code that makes things work in testing. Instead we should just make the tests work for the code, as is. One approach that's really similar but still abides by best practices is to create a variable (not parameter) in your actual func, use your inner func to set the variable and in testing, you can mock that inner function so your actual function's parameter is exactly as you expected. That also lets you do fine grain testing on things like clients that you need to mock out. – Adam Nov 18 '21 at 18:18
2

The only way I've seen this done is to dynamically create a copy of your outer function, modifying the function's code object constants with the code for your mocked function:

Does an equivalent of override exist for nested functions?

Community
  • 1
  • 1
Matthew Trevor
  • 14,354
  • 6
  • 37
  • 50
-2

Are you trying to replace a nested function with a mock object? If so, that's fairly straightforward, no matter how complicated the function is. You can use a MagicMock to replace pretty much any python object.

If you need to simulate a function that returns something, you can just set the MagicMock's return_value parameter. It would look something like this:

>>> super_nested_mock = mock.MagicMock()
>>> super_nested_mock.return_value = 42
>>> super_nested_mock()
42

However, if you're trying to test another piece of code that calls your super_nested function somewhere inside, and want to mock it out, you'll need to use a patch. In the mock library, it will look something like this:

with patch('super_nested') as super_nested_mock:
    super_nested_mock.return_value = "A good value to test with"
    assert my_function_that_calls_super_nested(5) == 20

Here, anything in the with block that would normally call super_nested instead will call the super_nested_mock and just return the value that you set to it.

There is some subtlety to what exactly you need to put in the patch call. Mainly, you want to patch the object as the module you're testing would see it. See "where to patch" for more instruction.

Wilduck
  • 13,822
  • 10
  • 58
  • 90
  • 3
    This won't work because the nested function only exists in the function I want to test. So `patch` can't locate and replace it directly. – satoru Sep 21 '12 at 02:31
  • I see, I guess I misunderstood what exactly you were trying to test. I'm going to leave this here for posterity's sake. Good luck though. – Wilduck Sep 21 '12 at 03:14