I'm working on writing tests for a flask application using pytest. I'm trying to write a unit test for this code. Basically I want to mock open()
so that it throws a FileNotFoundError when I call open the first time and check that write()
is called once.
try:
with open("config.json", "r") as file:
config = json.loads(file.read())
print(config)
except FileNotFoundError:
with open("config.json", "w") as file:
file.write(json.dumps({}))
This is my test:
import pytest
import mock
@pytest.fixture
def client(mocker):
return app.test_client()
@pytest.fixture
def mock_no_config(mocker):
m = mock.mock_open()
m.side_effect = [FileNotFoundError, None]
mock.patch("builtins.open", m)
return m
def test_no_config(client, mock_database_exists, mock_no_config):
response = client.get("/install/")
mock_no_config().write.assert_called_once_with("{}")
This is the output from pytest
====================================================================== FAILURES =======================================================================
___________________________________________________________________ test_no_config ____________________________________________________________________
client = <FlaskClient <Flask 'main'>>, mock_database_exists = None
mock_no_config = <MagicMock name='open' spec='builtin_function_or_method' id='139985038428816'>
def test_no_config(client, mock_database_exists, mock_no_config):
response = client.get("/install/")
> mock_no_config().write.assert_called_once_with("{}")
tests/test_web_installer.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/mock/mock.py:1100: in __call__
return _mock_self._mock_call(*args, **kwargs)
venv/lib/python3.10/site-packages/mock/mock.py:1104: in _mock_call
return _mock_self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_mock_self = <MagicMock name='open' spec='builtin_function_or_method' id='139985038428816'>, args = (), kwargs = {}
self = <MagicMock name='open' spec='builtin_function_or_method' id='139985038428816'>, effect = <list_iterator object at 0x7f50ce47f880>
result = <class 'FileNotFoundError'>
def _execute_mock_call(_mock_self, *args, **kwargs):
self = _mock_self
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
elif not _callable(effect):
result = next(effect)
if _is_exception(result):
> raise result
E FileNotFoundError
venv/lib/python3.10/site-packages/mock/mock.py:1165: FileNotFoundError
---------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------
<THE CONTENTS OF THE REAL config.json FILE HERE>
=============================================================== short test summary info ===============================================================
FAILED tests/test_web_installer.py::test_no_config - FileNotFoundError
It looks like the test is failing because a FileNotFoundError is being raised somewhere I'm not expecting it to, but since it's printing the contents of the real file, I would've assumed the mock wasn't taking effect.
What am I doing wrong?
Just to give an alternative without a separate class:
@pytest.fixture
def mock_no_config():
open_mock = MagicMock() # mock for open
file_mock = MagicMock() # mock for file returned from open
open_mock.return_value.__enter__.side_effect = [
FileNotFoundError, file_mock
]
with mock.patch("builtins.open", open_mock):
yield file_mock
def test_no_config(client, mock_database_exists, mock_no_config):
response = client.get("/install/")
mock_no_config.write.assert_called_once_with("{}")
Note that there are a few things in the original code that won't work:
patch
together with yield
prevents this (using mocker
is another possibility)open
, you have to add __enter__
to the mock (this is called by the context manager)None
as side effect won't work, as in this case write
would be called on None
- thus the second mock used for file