I use flask quite a lot for several projects
and over the last months my primary goal was to improve the code
coverage with unit tests. There, I came across several occasions where
the builtin open()
is used in a flask view.
A file is opened, its content gets read and the results are passed on to
a template. The following minimal example shows a typical usage:
#!/usr/bin/python3
from flask import Flask
app = Flask("MyFlaskApp")
@app.route("/")
def index():
try:
with open("/no/such/file/i/guess", 'r') as f:
content = f.read()
except FileNotFoundError:
content = "No such thing"
return content
When it comes to unit testing, the mock library is quite useful. As a
side note, since Python 3.3, mock is shipped as part of Python’s
unittest library. It also provides a helper function, mock_open()
to
easily mock calls to open()
. The usual code snippets out there mock
calls to open()
from (the global) builtins. This causes issues when
open()
should only be mocked for a particular module and stay
untouched for all other modules. The following test piece may be used to
mock out the call to open()
in the context of the
flask module to test. All other calls to
open()
stay untouched.
#!/usr/bin/python3
from mymodule import app
import unittest
from unittest.mock import mock_open, patch
class MyModule(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
def test_no_such_thing_on_file_not_found_error(self):
m = mock_open()
m.side_effect = FileNotFoundError()
with patch('mymodule.open', m, create=True):
rv = self.app.get('/')
self.assertTrue("No such thing", rv.data)
m.assert_called_once_with("/no/such/file/i/guess", "r")
The important piece of the above snippet is create=True
, which is
documented as
follows:
By default patch() will fail to replace attributes that don’t exist. If you pass in create=True, and the attribute doesn’t exist, patch will create the attribute for you when the patched function is called, and delete it again afterwards. This is useful for writing tests against attributes that your production code creates at runtime. It is off by default because it can be dangerous. With it switched on you can write passing tests against APIs that don’t actually exist!
Feel free to download the example code and run the tests with:
python3 -m unittest discover
Serve the application:
python3 -c 'from mymodule import app; app.run()'
Feedback? Contact me!