Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to patch a module's internal functions with mock?

By "internal function", I mean a function that is called from within the same module it is defined in.

I am using the mock library, specifically the patch decorators, in my unit tests. They're Django unit tests, but this should apply to any python tests.

I have one module with several functions, many of which call each other. For example (fictitious code, ignore the lack of decimal.Decimal):

TAX_LOCATION = 'StateName, United States'  def add_tax(price, user):     tax = 0     if TAX_LOCATION == 'StateName, UnitedStates':         tax = price * .75     return (tax, price+tax)  def build_cart(...):     # build a cart object for `user`     tax, price = add_tax(cart.total, cart.user)     return cart 

These are part of a deeper calling chain (func1 -> func2 -> build_cart -> add_tax), all of which are in the same module.

In my unit tests, I'd like to disable taxes to get consistent results. As I see it, my two options are 1) patch out TAX_LOCATION (with an empty string, say) so that add_tax doesn't actually do anything or 2) patch out add_tax to simply return (0, price).

However, when I try to patch either of these the patch seems to work externally (I can import the patched part inside the test and print it out, getting expected values), but seems to have no effect internally (the results I get from the code behave as if the patch were not applied).

My tests are like this (again, fictitious code):

from mock import patch from django.test import TestCase  class MyTests(TestCase):      @patch('mymodule.TAX_LOCATION', '')     def test_tax_location(self):         import mymodule         print mymodule.TAX_LOCATION # ''         mymodule.func1()         self.assertEqual(cart.total, original_price) # fails, tax applied      @patch('mymodule.add_tax', lambda p, u: (0, p))     def test_tax_location(self):         import mymodule         print mymodule.add_tax(50, None) # (0, 50)         mymodule.func1()         self.assertEqual(cart.total, original_price) # fails, tax applied 

Does anyone know if it's possible for mock to patch out functions used internally like this, or am I out of luck?

like image 354
eternicode Avatar asked Mar 17 '11 15:03

eternicode


People also ask

How do you mock a variable inside a function in Python?

When the test function then calls get_complex_data_structure() a mock object is returned and stored in the local name foo ; the very same object that mocked_function. return_value references in the above test; you can use that value to test if do_work() got passed the right object, for example.

What is mock patch?

mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

What is the difference between mock and patch?

Patching vs Mocking: Patching a function is adjusting it's functionality. In the context of unit testing we patch a dependency away; so we replace the dependency. Mocking is imitating. Usually we patch a function to use a mock we control instead of a dependency we don't control.


2 Answers

The answer: Clean up your darned imports

@patch('mymodule.TAX_LOCATION', '') did indeed patch things appropriately, but since our imports at the time were very haphazard -- sometimes we imported mymodule.build_cart, sometimes we imported project.mymodule.build_cart -- instances of the "full" import were not patched at all. Mock couldn't be expected to know about the two separate import paths... without being told explicitly, anyway.

We've since standardized all our imports on the longer path, and things behave much more nicely now.

like image 154
eternicode Avatar answered Oct 19 '22 11:10

eternicode


another option is to explicitly call patch on the function:

mock.patch('function_name') 

and to support both running directly or from py.test etc:

mock.patch(__name__ + '.' + 'function_name') 
like image 37
vim Avatar answered Oct 19 '22 10:10

vim