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())
Related
I am working on some functional tests for my application. Depending on the logged user's permissions, the sidebar will have different links. I am parameterizing them (hard coded) and running a test that works well (app is a webtest app):
endpoints = [
'/',
'/endpoint1',
'endpoint2',
...
]
#pytest.mark.parametrize('endpoint', endpoints)
def test_endpoints(endpoint, app):
res = app.get(endpoint).maybe_follow()
assert res.status_code == 200
I would like to avoid having to hard code the list of links for each type of user. Inside a fixture I can actually get them programmatically, so ideally I would like to parametrize the return value of this fixture in order to run the test function above:
#pytest.fixture
def endpoints(app):
res = app.get('/login').follow()
sidebar_links = []
for link in res.html.ul.find_all('a'):
if link.has_attr('href') and not link['href'].startswith('#'):
sidebar_links.append(link['href'])
return sidebar_links
Is this possible?
I would suggest that you use the pytest_configure() hook instead as this method will run before all your test methods. in conftest.py file you can keep a global variable as pytest.endpoints= [] then later in the hook method keep on appending the value of endpoints to this variable
something like this
pytest.endpoints= []
def pytest_configure(config,app):
res = app.get('/login').follow()
for link in res.html.ul.find_all('a'):
if link.has_attr('href') and not link['href'].startswith('#'):
pytest.endpoints.append(link['href'])
within the test method use the same variable as a parameter like below
#pytest.mark.parametrize("endpoint",pytest.endpoints)
def test_endpoints(endpoint):
Well i am not completely aware of your design so i cannot suggest any further but you can give this a try.
The firestore db has a structure like this:
collection: users
documents with id an user_id
each document has a sub-collection: games
each sub-collection has documents with id a game_id
My problem is that I can't loop over the first collection (---> for user in ref_users:) even my firestore db is already full with data organized like I said. Can someone help me understanding how can I retrieve the infos of these documents? Thanks a lot.
from flask import Flask, render_template
from google.cloud import firestore
db = firestore.Client()
#app.route('/', methods=['GET'])
def index():
ref_users = db.collection(u'users').get()
games = []
for user in ref_users:
print(f'{user.id} => {user.to_dict()}')
ref_game = db.collection(u'users').document(f'{user.id}').collection(u'games').get()
for rg in ref_game:
print(f'{rg.id} => {rg.to_dict()}')
tmp = rg.to_dict()
tmp['game_id'] = rg.id
tmp['user_id'] = user.id
games.append(tmp)
return render_template('game_list.html', title='Game list', games=games)
Implemented this code on my side with some minor changes and its working fine. So I think that you are maybe searching for solution to decrease computational complexity.
I think that collection_group might be helpful. Please check general documentation and Python reference.
I have created code for the same result as the sample (just index part). It has one loop less:
def index():
games = []
ref_game = db.collection_group(u'games').get()
for rg in ref_game:
print(f'{rg.id} => {rg.to_dict()}')
tmp = rg.to_dict()
tmp['game_id'] = rg.id
tmp['user_id'] = rg.reference.parent.parent.id
games.append(tmp)
return str(games)
I would use more sophisticated name for this subcollection like userGames or something. This is because of the fact that collection_group works on every collection with this name in whole database. If your system with grow, there is a chance that you will use the same name games in some other path not related to this logic. Than it will be included to this collection_group and may cause unexpected issues.
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!
I have an object that is used for fetching information from another service which is very simple. Since the object is simple and the initialization method could be easily patched I thought I would try to write my code to be super reusable and extendable. But alas, I cannot figure out how to make it work. The code below is pretty well sudo code and is super simplified but it should get the point across.
class SimpleClient:
def __init__(self):
pass
def read(self, key, path='some/path'):
return value_from_get_on_another_service
I then have a request handler object that initializes a client via get_client() (seen below)
def get_client():
return SimpleClient()
Then a method on the request handler uses the client.read() method a few times with different parameters (2nd dependent upon the 1st).
For my tests, I thought I could "patch" the get_client method to return my own simple object that could then be used "regularly" and eliminate the dependence on the third party service and actually use the values retrieved from the method execution. I was disappointed to find it was not that easy and clean. The test pattern is seen below.
class MockClient:
def __init__(self, addr='someAddr', token='someToken'):
pass
def read(self, value, prefix):
data = {}
if prefix == 'path/1':
data = self.p1_lookup(value)
elif prefix == 'path/2':
data = self.p2_lookup(value)
return self.response_wrapper(data)
def p2_lookup(self, key):
data = {
'key1': {
'sub_key': {"55B3FE7D-9F43-4DD4-9090-9D89330C918A": "Dev2",
"7A1C2F4B-E91C-4659-A33E-1B18B0BEE2B3": "Dev"}
}
}
return data.get(key, {})
#mock.patch('a.module.get_client')
def test_authorize_valid_request_no_body(mock_get_client):
request = RequestMock()
request.body = None
handler = RequestHandler(Application(), request=request, logging_level='INFO')
mock_get_client.return_value = MockClient()
handler.authorize_request()
assert handler.verified_headers is None
assert handler.verified_body is None
assert handler.user_authenticated is False
I have seen where I can mock the responses for the actual client.read() to return multiple values with a list. But this just seems like I will be doing lots of copy and paste and have to do the same thing over and over for each little test. Forgive me if this is simple, sadly I am just learning the art of testing. Is there a way to accomplish what I am trying to do? Maybe there is something super simple I am missing. Or maybe I am just totally on the wrong track for no good reason. Help?!
After a sleep, with fresh eyes I was able to figure this out relatively quickly thanks to a couple other similar questions/answers that I had not found before. Primarily this one, Python Mock Object with Method called Multiple Times.
Rather than needing to rebuild the module object completely I need to let mock do that for me and then override the specific method on it with the side_effect attribute. So below is what sanitized version of the code looks like.
def read_override(value, prefix):
lookup_data1 = {"lookup1": {'key1': 'value1'}}
lookup_data2 = {'some_id': {'akey': {'12345678': 'DEV'}}
data = {}
if prefix == 'path1/1a':
data = lookup_data1.get(value, {})
elif prefix == 'path2/2a':
data = lookup_data2.get(value, {})
return {'data': data}
# Create a true Mock of the entire LookupClient Object
VAULT_MOCK = mock.Mock(spec=LookupClient)
# make the read method work the way I want it to with an "override" of sorts
VAULT_MOCK.read.side_effect = vault_read_override
Then the test simply looked like this...
#mock.patch('a.module.get_client')
def test_authorize_valid_request_no_body(get_client):
get_client.return_value = VAULT_MOCK
request = RequestMock()
request.body = None
handler = RequestHandler(Application(), request=request, logging_level='INFO')
handler.authorize_request()
assert handler.verified_headers is None
assert handler.verified_body is None
assert handler.user_authenticated is False
I have a test class and a setup function that looks like this:
#pytest.fixture(autouse=True, scope='function')
def setup(self, request):
self.client = MyClass()
first_patcher = patch('myclass.myclass.function_to_patch')
first_mock = first_patcher.start()
first_mock.return_value = 'foo'
value_to_return = getattr(request, 'value_name', None)
second_patcher = patch('myclass.myclass.function_two')
second_mock = second_patcher.start()
second_mock.return_value = value_to_return
#could clean up my mocks here, but don't care right now
I see in the documentation for pytest, that introspection can be done for a module level value:
val = getattr(request.module, 'val_name', None)
But, I want to be able to specify different values to return based on the test I am in. So I am looking for a way to introspect the test_function not the test_module.
http://pytest.org/latest/fixture.html#fixtures-can-introspect-the-requesting-test-context
You can use request.function to get to the test function. Just follow the link on the b wepage you referenced to see what is available on the test request object :)
Maybe the documentation has changed since the time of the accepted answer.
At least for me it was not clear how to
Just follow the link
So I thought I'd update this thread with the link itself:
https://pytest.org/en/6.2.x/reference.html#request
Edit December 2021
Even when the link is correct now I think this statement from the pytest documentation is just not correct:
Fixture functions can accept the request object to introspect the “requesting” test function ...
While I found some examples for getting attributes of the module I did not find a single working example of introspecting the test function that requests the fixture. May be related to collection and runtime order.
What really helped me to get the desired behavior was to use the factory idiom a little further down in the pytest documentation:
https://pytest.org/en/6.2.x/fixture.html#factories-as-fixtures
Set up the fixture factory
#pytest.fixture(scope='function')
def getQueryResult() -> object:
def _impl(_mrId: int = 7622):
return QueryResult(_mrId)
return _impl
Usage
# Concrete value
def test_foo(getQueryResult):
queryResult = getQueryResult(4711)
...
# Default value
def test_bar(getQueryResult):
queryResult = getQueryResult()
...