Mocking a Sqlalchemy session for pytest - python

I don't know if this can be done but I'm trying to mock my db.session.save.
I'm using flask and flask-alchemy.
db.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
The unit test
def test_post(self):
with app.app_context():
with app.test_client() as client:
with mock.patch('models.db.session.save') as mock_save:
with mock.patch('models.db.session.commit') as mock_commit:
data = self.gen_legend_data()
response = client.post('/legends', data=json.dumps([data]), headers=access_header)
assert response.status_code == 200
mock_save.assert_called()
mock_commit.assert_called_once()
And the method:
def post(cls):
legends = schemas.Legends(many=True).load(request.get_json())
for legend in legends:
db.session.add(legend)
db.session.commit()
return {'message': 'legends saved'}, 200
I'm trying to mock the db.session.add and db.session.commit. I've tried db.session.save and legends.models.db.session.save and models.db.session.save. They all came back with the save error:
ModuleNotFoundError: No module named 'models.db.session'; 'models.db' is not a package
I don't get the error and I'm not sure how to solve it.
Or am I doing something that is totally wrong in wanting to mock a db.session?
Thanks.
Desmond

The problem you're running into here is better served by restructuring your code so that it's more testable rather than mocking out every component, or otherwise making a (very) slow integration test. If you get in the habit of writing tests in that way, then over time you'll end up with a slow build that will take too long to run, and you'll end up with fragile tests (good talk on the subject of why fast tests are important here).
Let's take a look at this route:
def post(cls):
legends = schemas.Legends(many=True).load(request.get_json())
for legend in legends:
db.session.add(legend)
db.session.commit()
return {'message': 'legends saved'}, 200
...and decompose it:
import typing
from flask import jsonify
class LegendsPostService:
def __init__(self, json_args, _session=None) -> None:
self.json_args = json_args
self.session = _session or db.session
def _get_legends(self) -> Legend:
return schemas.Legends(many=True).load(self.json_args)
def post(self) -> typing.List[typing.Dict[str, typing.Any]]:
legends = self._get_legends()
for legend in legends:
self.session.add(legend)
self.session.commit()
return schemas.Legends(many=True).dump(legends)
def post(cls):
service = LegendsPostService(json_args=request.get_json())
service.post()
return jsonify({'message': 'legends saved'})
Notice how we've isolated nearly all the points of failure from post into LegendsPostService, and further, we've removed all the flask internals from it as well (no global request objects floating around, etc). We've even given it the ability to mock out session if we need to for testing later on.
I would recommend you focus your testing efforts on writing test cases for LegendsPostService. Once you've got excellent tests for LegendsPostService, decide if you believe that even more test coverage will add value. If you do, then consider writing one simple integration test for post() to tie it all together.
The next thing you need to consider is how you want to think about SQLAlchemy objects in tests. I recommend just using FactoryBoy for auto-creating "mock" models for you. Here's a full application example for how to setup flask / sqlalchemy / factory-boy in this way: How do I produce nested JSON from database query with joins? Using Python / SQLAlchemy
Here's how I'd write a test for LegendsPostService (apologies as this is a bit hasty and doesn't perfectly represent what you're trying to do - but you should be able to adjust these tests for your use case):
from factory.alchemy import SQLAlchemyModelFactory
class ModelFactory(SQLAlchemyModelFactory):
class Meta:
abstract = True
sqlalchemy_session = db.session
# setup your factory for Legends:
class LegendsFactory(ModelFactory):
logo_url = factory.Faker('image_url')
class Meta(ModelFactory.Meta):
model = Legends
from unittest.mock import MagicMock, patch
# neither of these tests even need a database connection!
# so you should be able to write HUNDREDS of similar tests
# and you should be able to run hundreds of them in seconds (not minutes)
def test_LegendsPostService_can_init():
session = MagicMock()
service = LegendsPostService(json_args={'foo': 'bar'}, _session=session)
assert service.session is session
assert service.json_args['foo'] == 'bar'
def test_LegendsPostService_can_post():
session = MagicMock()
service = LegendsPostService(json_args={'foo': 'bar'}, _session=session)
# let's make some fake Legends for our service!
legends = LegendsFactory.build_batch(2)
with patch.object(service, '_get_legends') as _get_legends:
_get_legends.return_value = legends
legends_post_json = service.post()
# look, Ma! No database connection!
assert legends_post_json[0]['image_url'] == legends[0].image_url
I hope that helps!

Related

How to mock sqlalchemy model create method in pytest

I have an API which creates new flights in db, generating id and then inserting into db. I now need to mock this. Its an existing code so i don't want to change that.
# create_flight.py (Existing code)
from app.models.flight import Flight
flight=Flight()
flight.create_flight(json_data) # need to mock this ,
# this generates and commits in db and sets the flight object
response= { 'flight_id' : flight.flight_id }
My attempt of using pytest
#conftest.py
#pytest.fixture
def mock_response(monkeypatch):
def mock_create_flight(*args):
class flight:
flight_id=str(uuid.uuid4())
test=flight()
return test
monkeypatch.setattr('app.models.flight.Flight.create_flight',mock_create_flight)
it patches it correctly but i want the flight should have flight_id attribute set so that response dictionary sends the mocked flight id rather than going to db. I definitely doing something wrong here. not sure where. Thanks for looking
I got around it by importing the sqlalchemy model Flight in conftest , here is my solution
from app.models.flight import Flight
import uuid
#pytest.fixture(scope='session')
def get_flight_id():
yield str(uuid.uuid4())
#pytest.fixture(scope="session")
def mock_response(monkeypatch,get_flight_id):
def mock_create_flight(*args):
flight = Flight()
flight.flight_id = get_flight_id
flight.version_id = 1
return flight
monkeypatch.setattr('app.models.flight.Flight.create_flight',mock_create_flight)
This then returns mocked flight object in the original code

refactoring function to have a robust design

i am having a simple app example here:
say i have this piece of code which handles requests from user to get a list of books stored in a database.
from .handlers import all_books
#apps.route('/show/all', methods=['GET'])
#jwt_required
def show_books():
user_name = get_jwt_identity()['user_name']
all_books(user_name=user_name)
and in handlers.py i have :
def all_books(user_name):
db = get_db('books')
books = []
for book in db.books.find():
books.append(book)
return books
but while writing unit tests i realised if i use get_db() inside all_books() it would be harder to unit test the method.
so i thought this would be the good way.
from .handlers import all_books
#apps.route('/show/all', methods=['GET'])
#jwt_required
def show_books():
user_name = get_jwt_identity()['user_name']
db = get_db('books')
collection = db.books
all_books(collection=collection)
def all_books(collection):
books = []
for book in collection.find():
books.append(book)
return books
i want to know what is the good design to use?
have all code doing one thing at one place like the first example or the second example is good.
To me first one seems more clear as it has all related logic at one place. but its easier to pass a fake collection in second case to unit test it.
you should probably use the mock library see: https://docs.python.org/3/library/unittest.mock.html#quick-guide
(if you use python2 you will need pip install mock)
def test_it():
from unittest.mock import Mock,patch
with patch.object(get_db,'function',Mock(return_value=Mock(books=[1,2,3]))) as mocked_db:
x = get_db("ASDASD")
console.log(x.books)
# you can also do cool stuff like this
assert mocked_db.calledwith("ASDASD")
of coarse for yours you will have to construct a slightly more complex object
my_mocked_get_db = Mock(return_value=Mock(books=Mock(find=[1,2,3,4])))
with patch.object(get_db,'function',my_mocked_get_db) as mocked_db:
x = get_db("ASDASD")
print(x.books.find())

Mock flask.request in python nosetests

I'm writing test cases for code that is called via a route under Flask. I don't want to test the code by setting up a test app and calling a URL that hits the route, I want to call the function directly. To make this work I need to mock flask.request and I can't seem to manage it. Google / stackoverflow searches lead to a lot of answers that show how to set up a test application which again is not what I want to do.
The code would look something like this.
somefile.py
-----------
from flask import request
def method_called_from_route():
data = request.values
# do something with data here
test_somefile.py
----------------
import unittest
import somefile
class SomefileTestCase(unittest.TestCase):
#patch('somefile.request')
def test_method_called_from_route(self, mock_request):
# want to mock the request.values here
I'm having two issues.
(1) Patching the request as I've sketched out above does not work. I get an error similar to "AttributeError: 'Blueprint' object has no attribute 'somefile'"
(2) I don't know how to exactly mock the request object if I could patch it. It doesn't really have a return_value since it isn't a function.
Again I can't find any examples on how to do this so I felt a new question was acceptable.
Try this
test_somefile.py
import unittest
import somefile
import mock
class SomefileTestCase(unittest.TestCase):
def test_method_called_from_route(self):
m = mock.MagicMock()
m.values = "MyData"
with mock.patch("somefile.request", m):
somefile.method_called_from_route()
unittest.main()
somefile.py
from flask import request
def method_called_from_route():
data = request.values
assert(data == "MyData")
This is going to mock the entire request object.
If you want to mock only request.values while keeping all others intact, this would not work.
A few years after the question was asked, but this is how I solved this with python 3.9 (other proposed solutions stopped working with python 3.8 see here). I'm using pytest and pytest-mock, but the idea should be the same across testing frameworks, as long as you are using the native unittest.mock.patch in some capacity (pytest-mock essentially just wraps these methods in an easier to use api). Unfortunately, it does require that you set up a test app, however, you do not need to go through the process of using test_client, and can just invoke the function directly.
This can be easily handled by using the Application Factory Design Pattern, and injecting application config. Then, just use the created app's .test_request_context as a context manager to mock out the request object. using .test_request_context as a context manager, gives everything called within the context access to the request object. Here's an example below.
import pytest
from app import create_app
#pytest.fixture
def request_context():
"""create the app and return the request context as a fixture
so that this process does not need to be repeated in each test
"""
app = create_app('module.with.TestingConfig')
return app.test_request_context
def test_something_that_requires_global_request_object(mocker, request_context):
"""do the test thing"""
with request_context():
# mocker.patch is just pytest-mock's way of using unittest.mock.patch
mock_request = mocker.patch('path.to.where.request.is.used')
# make your mocks and stubs
mock_request.headers = {'content-type': 'application/json'}
mock_request.get_json.return_value = {'some': 'json'}
# now you can do whatever you need, using mock_request, and you do not
# need to remain within the request_context context manager
run_the_function()
mock_request.get_json.assert_called_once()
assert 1 == 1
# etc.
pytest is great because it allows you to easily setup fixtures for your tests as described above, but you could do essentially the same thing with UnitTest's setUp instance methods. Happy to provide an example for the Application Factory design pattern, or more context, if necessary!
with help of Gabrielbertouinataa on this article: https://medium.com/#vladbezden/how-to-mock-flask-request-object-in-python-fdbc249de504:
code:
def print_request_data():
print(flask.request.data)
test:
flask_app = flask.Flask('test_flask_app')
with flask_app.test_request_context() as mock_context:
mock_context.request.data = "request_data"
mock_context.request.path = "request_path"
print_request_data()
Here is an example of how I dealt with it:
test_common.py module
import pytest
import flask
def test_user_name(mocker):
# GIVEN: user is provided in the request.headers
given_user_name = "Some_User"
request_mock = mocker.patch.object(flask, "request")
request_mock.headers.get.return_value = given_user_name
# WHEN: request.header.get method is called
result = common.user_name()
# THEN: user name should be returned
request_mock.headers.get.assert_called_once_with("USERNAME", "Invalid User")
assert result == given_user_name
common.py module
import flask
def user_name():
return flask.request.headers.get("USERNAME", "Invalid User")
What you're trying to do is counterproductive. Following the RFC 2616 a request is:
A request message from a client to a server includes, within the first line of that message, the method to be applied to the resource, the identifier of the resource, and the protocol version in use.
Mocking the Flask request you need to rebuild its structure, what certainly, you will not to want to do!
The best approach should be use something like Flask-Testing or use some recipes like this, and then, test your method.

How to set up a testing mongo database for a flask application

I have a working flask application, which looks in the following way:
I have my myFolder/app.py with:
app = flask.Flask('name')
# and then a lot of routes
#app.route('/api/something', methods=['GET'])
def someName():
return flask.jsonify(somefile.somefunc())
the file with functions looks like:
# initialize database connection
client = pymongo.MongoClient(host=os.environ['MONGO_IP'])
db = client[os.environ['MONGO_DB']]
# and then a lot of functions that uses this global db to CRUD something
def somefunc():
# do something with db
return
it works fine and everyone is happy. But I want to write some tests with chai
I want to set up a testing database, populate it with data, make one of the API calls, assert that the data looks ok.
And I do this in the following way:
import chai
from myFolder import app
class App(chai.Chai):
#classmethod
def setUpClass(cls):
# this method is called before all testcases
os.environ['MONGO_DB'] = 'test_db' # changing the db to test one
#classmethod
def tearDownClass(cls):
# this method is called after all testcases
os.environ['MONGO_DB'] = 'normal' # change db to normal
def test_firstTestCase(self):
client = app.app.test_client()
print client.get('/api/something').data # here is the Problem
What surprises me is that the last print actually outputs the results from the real database (I expected it to output nothing, because I changed the database name to testing and have not populated it). So how should I properly set up a testing database?

Why does my flask unit test not use a tempfile database when I had specified so?

The test still writes to my MySQL database instead of a sqlite tempfile db. Why does this happen? Thanks!
Here's my code:
class UserTests(unittest.TestCase):
def setUp(self):
self.app = get_app()
#declare testing state
self.app.config["TESTING"] = True
self.db, self.app.config["DATABASE"] = tempfile.mkstemp()
#spawn test client
self.client = self.app.test_client()
#temp db
init_db()
def tearDown(self):
os.close(self.db)
os.unlink(self.app.config["DATABASE"])
def test_save_user(self):
#create test user with 3 friends
app_xs_token = get_app_access_token(APP_ID, APP_SECRET)
test_user = create_test_user(APP_ID, app_xs_token)
friend_1 = create_test_user(APP_ID, app_xs_token)
friend_2 = create_test_user(APP_ID, app_xs_token)
friend_3 = create_test_user(APP_ID, app_xs_token)
make_friend_connection(test_user["id"], friend_1["id"], test_user["access_token"], friend_1["access_token"])
make_friend_connection(test_user["id"], friend_2["id"], test_user["access_token"], friend_2["access_token"])
make_friend_connection(test_user["id"], friend_3["id"], test_user["access_token"], friend_3["access_token"])
save_user(test_user["access_token"])
This line might be the problem:
self.db, self.app.config["DATABASE"] = tempfile.mkstemp()
print out the values of self.db and self.app.config["DATABASE"] and makes sure they are what you expect them to be.
You probably want to investigate where your config self.app.config["DATABASE"] is referenced in your database code.
The Flask example code usually does a lot of work when the module is first imported. This tends to break things when you try to dynamically change values at run time, because by then its too late.
You probably will need to use an application factory so your app isn't built before the test code can run. Also, the app factory pattern implies you are using the Blueprint interface instead of the direct app reference, which is acquired using a circular import in the example code.

Categories

Resources