Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock PyMongo for testing with a Flask app?

I've found similar questions, but they seem to only cover mocking MongoDB and don't mention Flask.

I have a Flask app and I'm trying to unit test it with PyTest (including PyTest-Mongo and PyTest-Flask). However, before I can even get to the point of writing any tests, my test script crashes. The crash happens when importing the script with my Flash app: It's trying to create the PyMongo object without a url.

My question is: How can I ensure that PyMongo is mocked correctly at this point? According to the PyTest-Mongo documentation, the MongoDB test fixture should be passed to each of the test functions, but that doesn't help me if it's crashing on import.

test_app.py:

import pytest
import pytest_mongodb

from app import app


@pytest.fixture
def client():
    app.config['TESTING'] = True
    return client

app.py:

import ...

app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')

mongo = PyMongo(app)

...

if __name__ == '__main__':
app.run(host=os.environ.get('IP'),
        port=int(os.environ.get('PORT')),
        debug=False)
like image 917
Alex Meuer Avatar asked May 07 '19 19:05

Alex Meuer


2 Answers

we can wrap app and mongo in a function This works because mongo is used as a local variable.

app.py

from flask import Flask
from flask_pymongo import PyMongo

def get_app_with_config(config):

    app = Flask(__name__)
    app.config.from_object(config)
    mongo = PyMongo(app)

    @app.route("/")
    def index():
        pass
    .
    .
    
    return app, mongo

then we can create a test file and an application execution file with different databases:

test_app.py

from app import get_app_with_config
from config import TestConfig

app, mongo = get_app_with_config(TestConfig)

run.py

from app import get_app_with_config
from config import RunConfig

app, mongo = get_app_with_config(RunConfig)

if __name__ == '__main__':
    app.run(port=8000)

Sample of config.py file:

class RunConfig:

    MONGO_HOST = '192.168.1.37'
    MONGO_PORT = 27017
    MONGO_DBNAME = 'my_database'
    MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"

class TestConfig:

    MONGO_HOST = '192.168.1.37'
    MONGO_PORT = 27017
    MONGO_DBNAME = 'my_database_test'
    MONGO_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/{MONGO_DBNAME}"
    TESTING = True
like image 58
ansev Avatar answered Oct 11 '22 10:10

ansev


Needed a quick fix so I edited app.py so that it only hard-fails if PyMongo doesn't initialise when the file is executed (i.e. it ignores PyMongo's failed initialisation when running unit-tests.)

app = Flask(__name__)
app.config["MONGO_DBNAME"] = os.environ.get('DB_NAME')
app.config["MONGO_URI"] = os.environ.get('MONGO_URI')
app.secret_key = os.environ.get('SECRET')

try:
    mongodb = PyMongo(app).db
except ValueError:
    """We don't provide a URI when running unit tests, so PyMongo will fail to initialize.
    This is okay because we replace it with a version for testing anyway. """
    print('PyMongo not initialized!')
    mongodb = None
.
.
.

if __name__ == '__main__':
    if not mongodb:
        print('Cannot run. PyMongo failed to initialize. Double check environment variables.')
        exit(1)

    app.run(host=os.environ.get('IP'),
            port=int(os.environ.get('PORT')),
            debug=False)

In my tests file, I just assign the mocked mongoDB client to the app in the tests that need it. Definitely not the ideal solution.

def test_redacted(client, mongodb):
    app.mongodb = mongodb
    ...
like image 1
Alex Meuer Avatar answered Oct 11 '22 10:10

Alex Meuer