I am having some troubles in understanding how to properly mock a function using pytest-mock
module.
I will report a minimal reproducible example:
file: src/mini_handler.py
import tempfile
from src.mini_pdf_handler import mini_pdf_handler
def mini_handler():
tmp_file = tempfile.NamedTemporaryFile()
mini_pdf_handler()
return tmp_file.name
file: src/mini_pdf_handler.py
import tempfile
def mini_pdf_handler():
tmp_file = tempfile.NamedTemporaryFile()
return tmp_file.name
file: tests/test_handler.py
def test_mini_handler(mocker):
mock_tempfile = mocker.MagicMock()
mock_tempfile.return_value.name = 'outputs/output.pdf'
mocker.patch("src.mini_handler.tempfile.NamedTemporaryFile", side_effect=mock_tempfile)
mini_handler()
The problem is that the mock works, but it is mocking even the NamedTemporaryFile
in the mini_pdf_handler
module, when it should mock it only in the mini_handler
. I have the feeling the problem might be in the import policy that python have, since once a module is imported, it won't import it again. At the same time, I feel that the problem might be some silly oversight. Can someone help me?
The problem here is that you're patching "too deep".
Each module in src/
has a reference to the tempfile
module. You could mock src.mini_handler.tempfile
and it would not impact mini_pdf_handler
, but you're dereferencing src.mini_handler.tempfile
by mocking ...tempfile.NamedTemporaryFile
, and in both modules this refers to the same thing.
You can fix it like this:
from src.mini_handler import mini_handler
def test_mini_handler(mocker):
mock_tempfile = mocker.MagicMock()
mock_tempfile.return_value.name = 'outputs/output.pdf'
res = mocker.patch('src.mini_handler.tempfile')
res.NamedTemporaryFile = mock_tempfile
mini_handler()
Here we are replacing src.mini_handler.tempfile
with a mock object. This doesn't impact the reference to tempfile
in src.mini_pdf_handler
. We then configure the NamedTemporaryFile
attribute of our mock object.
In response to your comment:
Imagine we have the following dictionaries:
>>> tempfile = {'foo':'bar'}
>>> mini_handler = {'tempfile': tempfile}
>>> mini_pdf_handler = {'tempfile': tempfile}
If I modify `mini_handler['tempfile']['foo']...
>>> mini_handler['tempfile']['foo'] = 'qux'
...then that change is visible in both mini_handler['tempfile']
and mini_pdf_handler['tempfile']
, because both of these names refer to the same thing:
>>> mini_handler['tempfile']['foo']
'qux'
>>> mini_pdf_handler['tempfile']['foo']
'qux'
On the other hand, if I replace mini_handler['tempfile']
with a different dictionary:
>>> mini_handler['tempfile'] = {'something': 'else'}
This is no longer the case:
>>> mini_handler['tempfile']
{'something': 'else'}
>>> mini_pdf_handler['tempfile']
{'foo': 'qux'}
The situation with modules and mocking in your test is exactly analagous to this example. Does that help?