tyleax
tyleax

Reputation: 1780

Mocking confused with __init__.py module

I'm currently trying to use mock to test a package. Mock seems to not like it when I structure my python modules in different directories using __init__.py

My file tree is as follows:

.\pkg
  |_ module
       |_ __init__.py
       |_ module.py
  |_ tests
       |_ __init__.py
       |_ test_basic.py

When I try to mock the methods inside filesize using the following unit test:

@mock.patch('module.os')
def test_filesize(self, mock_os):
    class file_info:
        st_size = 1000

    mock_os.path.isfile.return_value = True
    mock_os.stat.return_value = file_info

    output = self.response.file_size("filename")

I get an error with a traceback of:

======================================================================
ERROR: test_filesize (__main__.cl_test_build_search_url)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python36\lib\site-packages\mock\mock.py", line 1297, in patched
    arg = patching.__enter__()
  File "C:\Python36\lib\site-packages\mock\mock.py", line 1369, in __enter__
    original, local = self.get_original()
  File "C:\Python36\lib\site-packages\mock\mock.py", line 1343, in get_original
    "%s does not have the attribute %r" % (target, name)
AttributeError: <module 'module' from 'C:\\Users\\username\\work\\open_source\\pkg\\module\\__init__.py'> does not have the attribute 'os'

I have imported the os module in module.py but I think it's getting confused because the __init__.py is confusing the interpreter?

I've looked at the mock.patch arguments available and

@mock.patch('module.os', create = True)

allowed the code to run but os doesn't end up being mocked at all and os.path.isfile and os.stat doesn't get overridden during run time.

Upvotes: 1

Views: 1822

Answers (2)

Jerinaw
Jerinaw

Reputation: 5529

I wanted to add a little more to @Gang's answer, since this bit me. This is going to be a little confusing because of the above example names but...

Example from above:

.\pkg
  |_ module
       |_ __init__.py
       |_ module.py
  |_ tests
       |_ __init__.py
       |_ test_basic.py

Lets say in the pkg.module.module you have a class named module (same as the python file name)

module.py

from time import time

class module:
    pass

and in your pkg.module __init__.py you do something like

from .module import module

__all__ = (
    "module",
)

If you want to patch time in the module.py:

mock.patch("pkg.module.module.time", ...)

Mock will get confused/blocked by the __init__.py and give you an error like

E AttributeError: <class 'pkg.module.module.module'> does not have the attribute 'time'

I had to change the name of the file or class. Or even just change the case, in the example above.

from time import time

class Module:
    pass

init.py

from .module import Module

__all__ = (
    "Module",
)

Upvotes: 1

Gang
Gang

Reputation: 2768

Try to change this:

@mock.patch('module.os', create = True)

to this:

# module(directory) module(module.py)
@mock.patch('module.module.os', create = True)

I am NOT good at explaining, here is a digest from mock.py explains:

target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch from. The target is imported when the decorated function is executed, not at decoration time.

Upvotes: 1

Related Questions