29

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?

eternicode
  • 6,805
  • 4
  • 33
  • 39
  • 4
    Any import inside of the test function *will* see the patched values, but I suspect the problem is still a "namespace" one. (i.e. where the patching is being done and where they are being used doesn't match). You're not showing the real code - mymodule.func1() isn't shown and where "cart" comes from isn't shown - so I can't diagnose the problem (I'm the author of mock). – fuzzyman May 24 '11 at 15:02
  • 1
    @fuzzyman, ironically, I've been completely unsuccessful so far in my attempts to produce a short example that reproduces the problem (I've tried several things in both simple python modules and a simple django project, everything behaves as expected). The project I'm using this in is a really huge django project, though, so at this point I believe there are some tainted things going on with the imports (from the example here, from within build_cart, `print sys.modules['app.views'].add_tax` prints a Mock object while `print add_tax` prints the original function). – eternicode Jul 12 '11 at 21:18
  • [Using mock patch.object to mock a class method](http://stackoverflow.com/questions/8469680/using-mock-patch-to-mock-a-class-method) – storm_m2138 Dec 30 '15 at 18:52

5 Answers5

22

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.

eternicode
  • 6,805
  • 4
  • 33
  • 39
15

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')
vim
  • 845
  • 16
  • 13
  • 2
    This is what I needed to mock a function that is defined in the same module – Jordan Epstein Dec 03 '18 at 18:40
  • The variable `__name__` is what I was looking for; it nicely obtains the module name `'__main__'` which works in a Jupyter notebook; thanks. – Hugues Apr 18 '22 at 21:06
4

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()
Dmitriy
  • 486
  • 5
  • 11
1

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.

Michael Kent
  • 1,736
  • 12
  • 11
  • 2
    If that's the case, why does the patched function behave as expected when called from within the test function? Besides that, I still get undesirable behavior when I omit the imports altogether. Thanks for the try, though. – eternicode Mar 18 '11 at 01:20
1

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.

Tom Ledger
  • 111
  • 2