Mocking open() in Python 3 unit tests
written on Sunday, April 19, 2015
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!