12

I got a function in a certain module that I want to redefine(mock) at runtime for testing purposes. As far as I understand, function definition is nothing more than an assignment in python(the module definition itself is a kind of function being executed). As I said, I wanna do this in the setup of a test case, so the function to be redefined lives in another module. What is the syntax for doing this? For example, 'module1' is my module and 'func1' is my function, in my testcase I have tried this (no success):

import module1

module1.func1 = lambda x: return True
Thiago Padilha
  • 4,590
  • 5
  • 44
  • 69

5 Answers5

13
import module1
import unittest

class MyTest(unittest.TestCase):
    def setUp(self):
        # Replace othermod.function with our own mock
        self.old_func1 = module1.func1
        module1.func1 = self.my_new_func1

    def tearDown(self):
        module1.func1 = self.old_func1

    def my_new_func1(self, x):
        """A mock othermod.function just for our tests."""
        return True

    def test_func1(self):
        module1.func1("arg1")

Lots of mocking libraries provide tools for doing this sort of mocking, you should investigate them as you will likely get a good deal of help from them.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
5
import foo

def bar(x):
    pass

foo.bar = bar
leoluk
  • 12,561
  • 6
  • 44
  • 51
2

Use redef: http://github.com/joeheyming/redef

import module1
from redef import redef

rd_f1 = redef(module1, 'func1', lambda x: True)

When rd_f1 goes out of scope or is deleted, func1 will go back to being back to normal

Jason Sturges
  • 15,855
  • 14
  • 59
  • 80
Joe Heyming
  • 777
  • 6
  • 11
  • I thought ref-counted scope was a CPython thing, and this kind of thing wouldn't work on other platforms (PyPy, Jython, IronPython). – leewz Apr 14 '14 at 19:14
  • all redef does is wrap a function or attribute with a new function/attribute. when the object goes out of scope, the __delete__ function is called, which reverts the function declaration back to using the old function/attribute. This is not a ref-counted scope kind of thing. – Joe Heyming Apr 14 '14 at 22:55
  • But the idea of "object is signaled when it goes out of scope" is dependent on CPython ref-counting, no? For example, in PyPy, when a file object's variable goes out of scope, the object is NOT garbage-collected right away, so the file is not closed until the collector decides to. – leewz Apr 14 '14 at 22:58
  • From what I understand of your code, it relies on `__del__` being called upon going out of scope, and also upon calling `del` (in `__exit__`). So I guess for the minority that doesn't use CPython, this module needs a caveat. (PyPy's explanation: http://pypy.readthedocs.org/en/latest/cpython_differences.html#differences-related-to-garbage-collection-strategies) – leewz Apr 14 '14 at 23:07
  • are there any alternatives to relying on del or exit? – Joe Heyming Apr 15 '14 at 16:59
  • Well, `__exit__` is guaranteed, but you're having `__exit__` rely on `del` calling `__del__`, which is the issue. You should directly call for cleanup upon `__exit__`. Otherwise, a `close()` to allow someone to explicitly clean up. The "automatic scope" way would still be allowed, but discouraged by your docs (as it is for `file` in even CPython). – leewz Apr 15 '14 at 21:54
  • ok, i changed the code to cleanup inside __exit__. Thanks @leewangzhong – Joe Heyming Apr 18 '14 at 20:34
  • I don't know if that's correct, though I also don't know if that's incorrect. AFAIK, `__del__` and `__exit__` mean separate things. I think I would make a `close()` that both of the others can call, and make it safe to call it multiple times (either by a flag or by idempotent operations). Also, `____` _usually_ shouldn't be called explicitly. – leewz Apr 19 '14 at 14:03
2

Just assign a new function or lambda to the old name:

>>> def f(x):
...     return x+1
... 
>>> f(3)
4
>>> def new_f(x):
...     return x-1
... 
>>> f = new_f
>>> f(3)
2

It works also when a function is from another module:

### In other.py:
# def f(x):
#    return x+1
###

import other

other.f = lambda x: x-1

print other.f(1)   # prints 0, not 2
sastanin
  • 40,473
  • 13
  • 103
  • 130
1

If you want to reload into the interpreter file foo.py that you are editing, you can make a simple-to-type function and use execfile(), but I just learned that it doesn't work without the global list of all functions (sadly), unless someone has a better idea:

Somewhere in file foo.py:

def refoo ():
  global fooFun1, fooFun2
  execfile("foo.py")

In the python interpreter:

refoo() # You now have your latest edits from foo.py