views:

59

answers:

4

I'm writing some unittests for code written by someone else here at the office. Python is not my strongest language. While I've been successful with basic unit tests, mocking in python is throwing me for a loop.

What I need to do is override a call to ConfigObj and inject my own mock config/fixture into any ConfigObj call.

settings.py

from configobj import ConfigObj
config = ConfigObj('/etc/myapp/config')

utils.py

from settings import config
"""lots of stuff methods using various config values."""

What I would like to do is, in my unittests for utils.py, inject myself either for ANY call to ConfigObj or settings.py itself.

Many of the mocking libraries expect me to Mock my own classes but in the case of this app, it doesn't have any explicit classes.

Can it be done or are the python namespace restrictions too strict that I can't intervene in what a module that I'm importing imports itself?

Side note: running 2.7 so I can't do any of the tricks I've read about from 2.5.

A: 

Couldn't you just overwrite the original function with another one?

There are no constants in Python, you can change everything, you could even do True = False.

leoluk
... or you can do `True, False = False, True`
Gabi Purcaru
+1  A: 

If the tests are in a separate file from from settings.py and utils.py you can create a file mock.py

import configobj

class MockConfigObj(object):
     #mock whatever you wan

configobj.ConfigObj = MockConfigObj

and then import mock before importing (from) any module that itself imports settings. This will ensure that settings.config is created with MockConfigObj. If you want a uniform global mocking, import it before any file that imports configobj.

This works because python will store configobj in sys.modules and check that before actually reading from a file on subsequent imports. in mock.py, the identifier ConfigObj is just a reference to the entry in sys.modules so that any changes that you make will be globally visible.

This strikes me as a little hacky though but it's the best that I can think of.

aaronasterling
Let me give this one a go. The key here is that I actually want to replace the ConfigObj with another ConfigObj of my own making. The namespace issues were what was throwing me off.
lusis
I think this one is going to work. I'm running into some errors with my mock object not being callable but I can see that when I do:import mockimport utilsthat it's calling my configobj instead which is what I wanted.
lusis
@lusis, if you provide more code samples, I can probably help if you haven't figured it out yet.
aaronasterling
FYI, here's how I got it working. It still feels *ugly*:http://gist.github.com/545953I still don't feel any closer to groking mocking under python. It took me several iterations to get past random errors (not subscriptable and such) before I got something that worked.I think the problem is the code I'm working with isn't object oriented. This really breaks a lot of the mock, mox, et. al. examples. I don't feel comfortable refactoring it until I have the unit tests in place to make sure I didn't actually break anything.
lusis
+1  A: 

Python namespaces are not strict at all within the same scope. Just override the variable name containing your object (or the class itself and provided it) within the same scope you'd be expecting the original and that is good enough.

Now, whether or not what you're replacing it with behaves the same is up to you...

jathanism
this will probably not work because `config` is already created using the real `ConfigObj` before `settings` is available to manipulate in the way you are describing. See my answer for the work around.
aaronasterling
A: 

I faced a similar situation before. Here is how I would go about addressing your problem.

Consider a test case for a function from utils.py.

import utils, unittest

class FooFunctionTests(unittest.TestCase):
    def setUp(self):
        utils._old_config = utils.config
        utils.config = MockClass()

    def tearDown(self):
        utils.config = utils._old_config
        del utils._old_config

    def test_foo_function_returns_correct_value(self):        
        self.assertEqual("success!", utils.foo())
Manoj Govindan
but doesn't any initialization in `utils` occur using the `config` that is an instance of the real `ConfigObj`?
aaronasterling
I'll give this one a shot but I don't think it will work because 'utils.py' bombs when settings.py can't load the config file (/etc/myapp/config).
lusis
@aaronasterling: Hmm. You are right. What do you think about patching `settings.py` instead? Or will it be impossible unless the source code of `settings.py` is changed to add a try/except around the init part?
Manoj Govindan
on the other hand, it does avoid the potential problem of having to mock out `ConfigObj` for _every_ consumer that my method incurs.
aaronasterling
@Manoj Govindan, I'm not sure how I feel about patching a module for the sake of unit testing. Also, I'm not sure what error would be `except` ed. As it stands, both of our answers have upsides and downsides.
aaronasterling