31

I want to fake a package in python. I want to define something so that the code can do

from somefakepackage.morefakestuff import somethingfake

And somefakepackage is defined in code and so is everything below it. Is that possible? The reason for doing this is to trick my unittest that I got a package ( or as I said in the title, a module ) in the python path which actually is just something mocked up for this unittest.

Adam Stelmaszczyk
  • 19,665
  • 4
  • 70
  • 110
Weholt
  • 1,889
  • 5
  • 22
  • 35
  • 4
    The word you're looking for is ___mock___. This question might help, http://stackoverflow.com/questions/295438 – deft_code Feb 25 '11 at 20:52

5 Answers5

46

Sure. Define a class, put the stuff you need inside that, assign the class to sys.modules["classname"].

class fakemodule(object):

    @staticmethod
    def method(a, b):
        return a+b

import sys
sys.modules["package.module"] = fakemodule

You could also use a separate module (call it fakemodule.py):

import fakemodule, sys

sys.modules["package.module"] = fakemodule
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 6
    One trick with this is to make sure to also insert a fake module for `package` with `__path__ = []` as a top-level attribute. Without that, the module hierarchy is incomplete, which may confusing some tools. – ncoghlan Feb 26 '11 at 15:34
  • 2
    Just to add on. If you're importing a sub-module, e.g. `import a.b.c`, you may need to assign fake modules to `sys.modules["a"]`, `sys.modules["a.b"]` and `sys.modules["a.b.c"]` – Kevin Lee Jun 09 '16 at 15:59
20

Yes, you can make a fake module:

from types import ModuleType
m = ModuleType("fake_module")

import sys
sys.modules[m.__name__] = m

# some scripts may expect a file
# even though this file doesn't exist,
# it may be used by Python for in error messages or introspection.
m.__file__ = m.__name__ + ".py"

# Add a function
def my_function():
    return 10

m.my_function = my_function

Note, in this example its using an actual module (of ModuleType) since some Python code may expect modules, (instead of a dummy class).

This can be made into a utility function:

def new_module(name, doc=None):
    import sys
    from types import ModuleType
    m = ModuleType(name, doc)
    m.__file__ = name + '.py'
    sys.modules[name] = m
    return m

print(new_module("fake_module", doc="doc string"))

Now other scripts can run:

import fake_module
ideasman42
  • 42,413
  • 44
  • 197
  • 320
8

I took some of the ideas from the other answers and turned them into a Python decorator @modulize which converts a function into a module. This module can then be imported as usual. Here is an example.

@modulize('my_module')
def my_dummy_function(__name__):  # the function takes one parameter __name__
    # put module code here
    def my_function(s): 
        print(s, 'bar')

    # the function must return locals()
    return locals()

# import the module as usual
from my_module import my_function
my_function('foo') # foo bar

The code for the decorator is as follows

import sys
from types import ModuleType

class MockModule(ModuleType):
    def __init__(self, module_name, module_doc=None):
        ModuleType.__init__(self, module_name, module_doc)
        if '.' in module_name:
            package, module = module_name.rsplit('.', 1)
            get_mock_module(package).__path__ = []
            setattr(get_mock_module(package), module, self)

    def _initialize_(self, module_code):
        self.__dict__.update(module_code(self.__name__))
        self.__doc__ = module_code.__doc__

def get_mock_module(module_name):
    if module_name not in sys.modules:
        sys.modules[module_name] = MockModule(module_name)
    return sys.modules[module_name]

def modulize(module_name, dependencies=[]):
    for d in dependencies: get_mock_module(d)
    return get_mock_module(module_name)._initialize_

The project can be found here on GitHub. In particular, I created this for programming contests which only allow the contestant to submit a single .py file. This allows one to develop a project with multiple .py files and then combine them into one .py file at the end.

Jason Rute
  • 220
  • 2
  • 5
  • I can't get your examples to work at the point of `from my_module import my_function`. Simply doing `import my_module` does work. Is this specific to one python version or something? – AlanSE Mar 13 '18 at 12:33
  • @AlanSE While I designed this for Python 3, I just pasted the above code into a Python 2.7.10 interpreter and it worked fine. What was your specific error? Did you run the code above exactly (running the second code block before the first)? – Jason Rute Mar 13 '18 at 13:32
5

You could fake it with a class which behaves like somethingfake:

try:
    from somefakepackage.morefakestuff import somethingfake
except ImportError:
    class somethingfake(object):
         # define what you'd expect of somethingfake, e.g.:
         @staticmethod
         def somefunc():
             ...
         somefield = ...
Oben Sonne
  • 9,893
  • 2
  • 40
  • 61
5

TL;DR

Patch sys.modules using unittest.mock:

mock.patch.dict(
    sys.modules,
    {'somefakepackage': mock.Mock()},
)

Explanation

Other answers correctly recommend to fix sys.modules but a proper way is to temporarily patch it by using mock.patch. It will replace a module only for the time tests are run with a fake object that imitates the desired behaviour. And restore it back once tests are finished to not affect other test cases.

The code in TL;DR section will simply make your missing package not raise ImportError. To provide fake package with contents and imitate desired behaviour, initiate mock.Mock(…) with proper arguments (e.g. add attributes via Mock's **kwargs).

Full code example

The code below temporarily patches sys.modules so that it includes somefakepackage and makes it importable from the dependent modules without ImportError.

import sys
import unittest
from unittest import mock

class SomeTestCase(unittest.TestCase):
    def test_smth(self):
        # implement your testing logic, for example:
        self.assertEqual(
            123,
            somefakepackage_dependent.some_func(),
        )

    @classmethod
    def setUpClass(cls):  # called once before all the tests
        # define what to patch sys.modules with
        cls._modules_patcher = mock.patch.dict(
            sys.modules,
            {'somefakepackage': mock.Mock()},
        )
        # actually patch it
        cls._modules_patcher.start()
        # make the package globally visible and import it,
        #   just like if you have imported it in a usual way
        #   placing import statement at the top of the file,
        #   but relying on a patched dependency
        global somefakepackage_dependent
        import somefakepackage_dependent

    @classmethod  # called once after all tests
    def tearDownClass(cls):
        # restore initial sys.modules state back
        cls._modules_patcher.stop()

To read more about setUpClass/tearDownClass methods, see unittest docs.

unittest's built-in mock subpackage is actually a very powerful tool. Dive deeper into its documentation to get a better understanding.

Anton Bryzgalov
  • 1,065
  • 12
  • 23