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

Question:

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?

Asked By: eternicode

||

Answers:

I’m pretty sure your problem is that you are importing ‘mymodule’ inside your test functions, and therefore the patch decorator has no chance of actually patching. Do the import at the top of the module, like any other import.

Answered By: Michael Kent

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.

Answered By: 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')
Answered By: vim

I’d like to add solution other than accepted one. You can also patch the module before it’s been imported in any other modules and remove patch at the end of your test case.

#import some modules that don't use module you are going to patch
import unittest
from mock import patch
import json
import logging
...


patcher = patch('some.module.path.function', lambda x: x)
patcher.start()

import some.module.path

class ViewGetTests(unittest.TestCase):

  @classmethod
  def tearDownClass(cls):
      patcher.stop()
Answered By: Dmitriy

If your module is in a folder with an __init__.py file that has from [module_file] import * make sure your patch argument has the folder and file name (module_folder.module_file), or the patch will succeed (no ‘module does not have this attribute’ error) but not function (calls will go to the actual function not the mock), no matter how the function under test is imported.

Answered By: Tom Ledger
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.