Mocking `from foo import *` functions

hsoft at hardcoded.net hsoft at hardcoded.net
Sat Jan 10 01:12:57 EST 2009


On Jan 10, 4:21 am, Silfheed <silfh... at gmail.com> wrote:
> So I'm in the current testing situation:
>
> sender.py:
> -------------
> def sendEmails():
>    return "I send emails"
>
> alerter.py:
> -------------
> from sender import *
> def DoStuffAndSendEmails():
>   doStuff()
>   sendEmails()
>
> I'm trying to write a test fn that will test DoStuffAndSendEmails()
> (as well as it's kin) without actually sending any emails out.  I
> could go through alter alerter so that it does `import sender` and
> then find and replace fn() with sender.fn() so I can just create a
> mock fn fakeSendEmails() and and do something like sender.sendEmails =
> fakeSendEmails, but I'd rather not.
>
> Anyone know how to test alerter.py with out altering the file?
>
> Thanks!

Don't ever put testing flag in your non-code, that's very ugly and
creates a weird circular dependency between your real code and your
test code. It will give you headaches later.

To answer to Rob: yeah, sure that would work, but I always thought
mocking the imported function didn't feel right. The test then depends
on the import method of the tested module. If you later change your
mind and decide to use "import sender" and then "sender.sendEmails()",
you have to change your test code.

In my code, I have a custom TestCase class which has a method for
dealing with this stuff, here's the code relevant to your problem:

class TestCase(unittest.TestCase):
    cls_tested_module = None

    def run(self, result=None):
        self._mocked = []
        unittest.TestCase.run(self, result)
        # We use reversed() so the original value is put back, even if
we mock twice.
        for target, attrname, old_value in reversed(self._mocked):
            setattr(target, attrname, old_value)

    def mock(self, target, attrname, replace_with):
        ''' Replaces 'target' attribute 'attrname' with 'replace_with'
and put it back to normal at
            tearDown.

            The very nice thing about mock() is that it will scan
self.cls_tested_module for the
            mock target and mock it as well. This is to fix the "from"
imports problem (Where even
            if you mock(os, 'path'), if the tested module imported it
with "from os import path",
            the mock will not work).
        '''
        oldvalue = getattr(target, attrname)
        self._mocked.append((target, attrname, oldvalue))
        setattr(target, attrname, replace_with)
        if (self.cls_tested_module is not None) and
(self.cls_tested_module is not target):
            for key, value in self.cls_tested_module.__dict__.iteritems
():
                if value is oldvalue:
                    self.mock(self.cls_tested_module, key,
replace_with)

When you use it, set cls_tested_module (at the class level) to
"sender" (not the string, the module instance)



More information about the Python-list mailing list