D. Joe
D. Joe

Reputation: 63

Unit testing Flask app - mocking global variables in the app

I've got a Flask app module (app.py) which looks like this

# imports
...
from flask import Flask, request, Response
...

# module-level vars, including `logger` and `APP`
...
logger = None
APP = None
...

def init():
    """
    Initialisation of app resources, including `logger`
    """
    ...
    APP = Flask(__name__) 
    ...
    logger = logging.getLogger()
    ...
    ...

try:
    init()
except Exception as e:
    logger.error(str(e))

@APP.route('/healthcheck', methods=['GET'])
def healthcheck():
    """
    Healthcheck endpoint - just returns OK if the app
    initialised OK.
    """
    return 'OK'

@APP.route('/get_keys', method=['POST'])
def get_keys():
    """
    Main endpoint - accepts a POST request from a client
    containing either a CSV or JSON payload defining a set
    of geographic locations, and then returns some "keys"
    for these.
    """
    try:
        logger.info('Extracting payload')
        # extract payload
        logger.info('Processing for keys')
        # do some stuff
        ...
        ...
    except Exception as e:
        logger.error("Error: {}.".format(str(e)))

    # return response

I've got unit tests for the Flask app defined in a module AppTests in the tests subpackage.

# general imports including `unittest` etc.
# import app module as `app`

class AppTests(unittest.TestCase):
    """
    Flask app tests
    """

    @classmethod
    def setUpClass(self):

        app.APP.config['TESTING'] = True
        app.APP.config['DEBUG'] = False

        self.app = app.APP.test_client()

        # define other resources needed for `self.app`


    def test_healthcheck(self):

        res = self.app.get(path='/healthcheck')

        self.assertEqual(res.status_code, 200)

    def test_get_keys__csv(self):

        # define sample csv data in `data` variable

        headers = {
            'Accept-Encoding': 'identity,deflate,gzip,compress',
            'Content-Type': 'text/csv; charset=utf-8',
            'Content-Length': len(data)
        }

        res = self.app.post(path='/get_keys', headers=headers.items(), data=data)

        self.assertEqual(res.status_code, 200)

The test for the healthcheck endpoint passes but the test for the get_keys endpoint fails.

$ python -m unittest -v AppTests.AppTests.test_get_keys__csv
test_get_keys__csv (AppTests.AppTests) ... 
ERROR

======================================================================
ERROR: test_get_keys__csv (AppTests.AppTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "AppTests.py", line 105, in test_get_keys__csv
    res = self.app.post(path='/get_keys', headers=headers.items(), data=data)
  File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 801, in post
    return self.open(*args, **kw)
  File "/path/to/venv/lib/python2.7/site-packages/flask/testing.py", line 127, in open
    follow_redirects=follow_redirects)
  File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 764, in open
    response = self.run_wsgi_app(environ, buffered=buffered)
  File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 677, in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
  File "/path/to/venv/lib/python2.7/site-packages/werkzeug/test.py", line 884, in run_wsgi_app
    app_rv = app(environ, start_response)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1994, in __call__
    return self.wsgi_app(environ, start_response)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/path/to/venv/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/path/to/app.py", line 205, in get_keys
    logger.error("Error: {}.".format(str(e)))
AttributeError: 'NoneType' object has no attribute 'error'

----------------------------------------------------------------------
Ran 1 test in 0.036s

FAILED (errors=1)

It looks like the reference to the logger object in the get_keys endpoint in the app is null when I make the call to self.app.post('/get_keys, headers=headers.items(), data=data). Every call to logger.info is generating an exception in the endpoint, which is caught and logged, and that's what I am seeing when I run the endpoint test.

Is there a way to mock this, or some how bypass the use of logger from the tests module itself? I would rather not modify the endpoint method itself.

Upvotes: 3

Views: 2313

Answers (1)

Will Keeling
Will Keeling

Reputation: 22994

You could potentially mock out the logging import when you run test_get_keys__csv().

from unittest.mock import patch

@patch('path.to.app.logging')  # Mock the logging import
def test_get_keys__csv(self, mock_logging):

    # define sample csv data in `data` variable

    headers = {
        'Accept-Encoding': 'identity,deflate,gzip,compress',
        'Content-Type': 'text/csv; charset=utf-8',
        'Content-Length': len(data)
    }

    res = self.app.post(path='/get_keys', headers=headers.items(), data=data)

    self.assertEqual(res.status_code, 200)

If you're using Python 2, mock is a separate install.

pip install mock

and then import with

from mock import patch

More info on mock: https://docs.python.org/3/library/unittest.mock.html

Upvotes: 2

Related Questions