How to mock nested functions?

2019-01-26 04:30发布

问题:

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.

回答1:

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



回答2:

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


回答3:

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.



回答4:

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?