Mocking `from foo import *` functions

S

Silfheed

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!
 
M

MRAB

Silfheed said:
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?
You could alter sender.py. :)

Actually, you could have:

alerter.py:
-------------
TEST = False
if TEST:
from mock_sender import *
else:
from sender import *


so you're changing alerter.py in only one place, or read the value of
TEST from a config file.
 
H

hsoft

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)
 
H

hsoft

Note that doing the above *before* any other module imports
from sender, will be sufficient in *any* case, though it won't
help if the tested code calls reload( sender ).

Yeah, of course, but most of the time, you want to mock at the *test*
level, (you don't want your mock to affect all the tests in your
module), so you can't just mock the function before you import your
tested module. That is why I created this handy TestCase.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,150
Latest member
MakersCBDReviews
Top