I am starting to use a python mock library for my testing. I want to mock a module that is imported within the namespace of the module under test without actually importing it or requiring that it exist first (i.e. throwing an ImportError).
Suppose the following code exists:
foo.py
import helpers
def foo_func():
return helpers.helper_func()
The goal is to test foo_func() when 'helpers.py' does not exist anywhere, and if it does exist, act as if it doesn't.
First try, using the super cool @patch decorator:
from mock import patch, sentinel
import foo
@patch("foo.helpers")
def foo_test(mock):
mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
This works if the "helpers" module can be imported. If it doesn't exist, I get an ImportError.
Next attempt with patch, sans decorator:
from mock import patch, sentinel, Mock
import foo
helpers_mock = patch("foo.helpers")
helpers_mock.start()
def foo_test():
helpers_mock.helper_func = Mock('helper_func')
helpers_mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
Again, this doesn't work if "helpers" is missing... and, if it exists, the assertion fails. Not really sure why that happens.
Third attempt, current solution:
import sys
helpers_mock = Mock(name="helpers_mock", spec=['helper_func'])
helpers_mock.__name__ = 'helpers'
sys.modules['helpers'] = helpers_mock
import foo
def foo_test():
helpers_mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
This test passes regardless of whether or not "helpers.py" exists.
Is this the best way to accomplish this goal? Does the mocking library I am using provide an alternative to this? What other ways can I do this?
You're kind of missing the point of what a Mock is. You're supposed to build them when you want an object with a particular interface, regardless of how it's implemented.
What you're doing is trying to re-implement python's module system, plus it's a pretty horrible abuse of global variables to boot.
Instead of making foo a module, make a Foo class, and pass in the helpers in the constructor.
class Foo(object):
def __init__(self, helpers):
self.helpers = helpers
# then, instead of import foo:
foo = Foo(mock_helpers)
Even if the real "helpers" is actually going to be a module, there is no reason you need to be messing with sys.modules and setting it up via import
in your tests.
And if foo has to be a module, that's fine too, but you do it like this:
# foo.py
class Foo(object):
pass # same code as before, plus foo_func
try:
import whatever
_singleton = Foo(whatever)
except ImportError:
_singleton = Foo(something_else)
def foo_func():
return _singleton.foo_func()
Large chunks of the standard library work this way. It's pretty much the standard for defining singleton-like modules.
I had a similar problem where the helpers
library couldn't be loaded as it needed special hardware.
Rather than make radical changes to the code that you want to test, an alternative is to insert a "fake" directory into sys.path
as suggested by how to add a package to sys path for testing
import os, sys
fake_dir = os.path.join(os.path.dirname(__file__), 'fake')
assert(os.path.exists(fake_dir))
sys.path.insert(0, fake_dir)
import foo
from unittest.mock import sentinel
def foo_test():
foo.helpers.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
where fake
is structured as:
.
├── fake/
│ └── helpers/
│ └── __init__.py
├── foo.py
└── helpers/
and __init__.py
has
from unittest.mock import Mock
helper_func = Mock()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With