Unit testing a Flask application class - python

I'm fairly new to Python, so my apologies in advance if this is much ado for something basic.
I have situation similar to How do you set up a Flask application with SQLAlchemy for testing? The big difference for me is that unlike most other Flask examples I see on the Internet, most of the code I have for my application is in a class. For some reason, this is causing my unit testing to not work correctly. Below is a basic setup of my application and tests:
Application:
from Flask import Flask
app = Flask(__name__)
class MyApplication():
def __init__(self, param1, param2):
app.add_url("/path/<methodParam>", "method1", self.method1, methods=["POST"])
# Initialize the app
def getApplication(self):
options = # application configuration options
middleware = ApplicationMiddleware(app, options)
return middleware
def method1(self, methodParam):
# Does useful stuff that should be tested
# More methods, etc.
Application Tests:
import unittest
from apppackage import MyApplication
class ApplicationTestCase(unittest.TestCase):
def setUp(self):
self.tearDown()
param1 = # Param values
param2 = # Param values
# Other local setup
self.app = MyApplication(param1, param2).app.test_client()
def tearDown(self):
# Clean up tests
def test_method1(self):
methodParam = # Param value
response = self.app.post("path/methodParam")
assert(reponse.status_code == 200)
When I run my tests via
nosetests --with-coverage --cover-package apppackage
./test/test_application.py
I get the following error:
param2).app.test_client() AttributeError: MyApplication instance has
no attribute 'app'
I've tried moving app inside the class declaration, but that doesn't do any good, and isn't how every other unit testing guide I've seen does it. Why can't my unit tests find the "app" attribute?

Your unit test cannot find the "app" attribute because MyApplication does not have one. There is an "app" attribute in the module where MyApplication is defined. But those are two separate places.
Perhaps try the following:
class MyApplication(object):
def __init__(self, param1, param2):
self.app = Flask(__name__)
self.app.add_url("/path/<methodParam>", "method1", self.method1, methods=["POST"])
# Initialize the app
Alternatively, you also seem to have a "getApplication" method which you aren't really doing anything with, but I imagine that you're using it for something. Perhaps you actually want this in your test...
def setUp(self):
self.tearDown()
param1 = # Param values
param2 = # Param values
# Other local setup
self.app = MyApplication(param1, param2).getApplication().test_client()

Related

Pytest: Inherit fixture from parent class

I have a couple of test cases to test the endpoints of a flask/connexion based api.
Now I want to reorder them into classes, so there is a base class:
import pytest
from unittest import TestCase
# Get the connexion app with the database configuration
from app import app
class ConnexionTest(TestCase):
"""The base test providing auth and flask clients to other tests
"""
#pytest.fixture(scope='session')
def client(self):
with app.app.test_client() as c:
yield c
Now I have another class with my actual testcases:
import pytest
from ConnexionTest import ConnexionTest
class CreationTest(ConnexionTest):
"""Tests basic user creation
"""
#pytest.mark.dependency()
def test_createUser(self, client):
self.generateKeys('admin')
response = client.post('/api/v1/user/register', json={'userKey': self.cache['admin']['pubkey']})
assert response.status_code == 200
Now unfortunately I always get a
TypeError: test_createUser() missing 1 required positional argument: 'client'
What is the correct way to inherit the fixture to subclasses?
So after googling for more infos about fixtures I came across this post
So there were two required steps
Remove the unittest TestCase inheritance
Add the #pytest.mark.usefixtures() decorator to the child class to actually use the fixture
In Code it becomes
import pytest
from app import app
class TestConnexion:
"""The base test providing auth and flask clients to other tests
"""
#pytest.fixture(scope='session')
def client(self):
with app.app.test_client() as c:
yield c
And now the child class
import pytest
from .TestConnexion import TestConnexion
#pytest.mark.usefixtures('client')
class TestCreation(TestConnexion):
"""Tests basic user creation
"""
#pytest.mark.dependency(name='createUser')
def test_createUser(self, client):
self.generateKeys('admin')
response = client.post('/api/v1/user/register', json={'userKey': self.cache['admin']['pubkey']})
assert response.status_code == 200

Flask app created twice during python unittest

I have an app.py file which creates an flask app
def create_app():
app = Flask(__name__)
return app
I am trying to write an unittest for my module and below is the file
from app import create_app
class TestCase(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.client = self.app.test_client()
ctx = self.app.app_context()
ctx.push()
def test_healthcheck(self):
res = self.client.get("/")
self.assertEqual(res.status_code, 200)
def test_tenant_creation(self):
res = self.client.post("/tenants")
self.assertEqual(res.status_code, 200)
When i run individual test methods it is working fine. But when i run the entire test case , the create app is called again which causes issues since my create app has dependencies which needs to be called only once.
Is it possible to create app only once ?
setUp gets called before each test method. Therefore, if you run the whole test case, it will be called twice (one for each test method).
To run something only once for the TestCase, you could try overriding the __init__ method (see this SO question), or setUpClass or setUpModule. YMMV depending on which python version and test runners you are using.
IMO, the problem may related with context. You should create a tearDown() method to destroy the application context you created in setUp():
class TestCase(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.client = self.app.test_client()
self.ctx = self.app.app_context()
self.ctx.push()
def tearDown(self):
self.ctx.pop()

Mocking render to response with Pyramid

I have a decorator that looks like so:
def validate_something(func):
def validate_s(request):
if request.property:
render_to_response('template.jinja', 'error'
return func(request)
return validate_something
I'm trying to test it like so. I load the local WSGI stack as an app.
from webtest import TestApp
def setUp(self):
self.app = TestApp(target_app())
self.config = testing.setUp(request=testing.DummyRequest)
def test_something(self):
def test_func(request):
return 1
request = testing.DummyRequest()
resp = validate_something(test_func(request))
result = resp(request)
The error I'm getting is (being generated at the innermost render_to_response):
ValueError: no such renderer factory .jinja
I understand that I need to mock render_to_response, but I'm at a bit of a loss as to how to exactly do that. If anyone has any suggestions, I would greatly appreciate it.
Mock library is awesome:
mock provides a core Mock class removing the need to create a host of
stubs throughout your test suite. After performing an action, you can
make assertions about which methods / attributes were used and
arguments they were called with. You can also specify return values
and set needed attributes in the normal way.
Additionally, mock provides a patch() decorator that handles patching
module and class level attributes within the scope of a test
Youc code would look like this:
def test_something(self):
test_func = Mock.MagicMock(return_value=1) # replaced stub function with a mock
request = testing.DummyRequest()
# patching a Pyramid method with a mock
with mock.patch('pyramid.renderers.render_to_response' as r2r:
resp = validate_something(test_func(request))
result = resp(request)
assert r2r.assert_called_with('template.jinja', 'error')
The following worked for me:
def setUp(self):
self.app = TestApp(target_app())
self.config = testing.setUp(request=testing.DummyRequest)
self.config.include('pyramid_jinja2')
By setting up the config to include jinja your tests can then find your template and the jinja environment. You may also need to provide a test version of the template in the same folder as your tests. If you get a message such as TemplateNotFound on running the tests then make sure a version of the template is located in the correct place.

Flask initialisation for unit test and app

I've got a Flask application which I'd like to run some unit tests on. To do so, I create a new Flask object, initialise blueprints, SQLA and a few other packages and execute the test case.
However, I've noticed that some endpoints on the test flask object are missing, which made me wonder about the general way of how initialisation is handled in flask.
Taking a minimal example, an endpoint would be created like so:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
If I was to create a new Flask object somewhere in my testcase's setUp method, it would most certainly not contain a route '/' as this route was created from another flask object (the one from the file above). So my question is: How should a test case be written and how is the initialisation meant to work in general? Somewhere I read that one should avoid initialisation at import (i.e. on a module level), but this seems to be impossible if annotations are used.
You don't create a new Flask object in your test cases. You import your existing app instead.
In many project setups you already added all your extensions to that app. In many of mine I have a factory method that'll take a configuration object and returns the fully initialized app object for me; I use this to create a base test case:
import unittest
import project
class Config(object):
DEBUG = False
TESTING = True
CACHE_NO_NULL_WARNING = True # silence Flask-Cache warning
SECRET_KEY = 'SECRET_KEY'
class ProjectCoreViewCase(unittest.TestCase):
"""Base test case for the Project core app"""
def setUp(self, **kwargs):
config = Config()
config.__dict__.update(kwargs)
app = project.create_app(config)
self.app = app.test_client()
and any tests can then use self.app as the test client in all tests.
This is a base test case, you'd inherit from it; the setUp() method allows for additional configuration to be set, by passing in keyword arguments to a super() call:
class ConcreteTestCase(ProjectCoreViewCase):
def setUp(self):
super(ConcreteTestCase, self).setUp(
SQLALCHEMY_DATABASE_URI='your_test_specific_connection_uri',
)

Can I have all of my unit tests run in the Flask app context?

For every test I write that uses my apps models, I seem to have to use the current apps context:
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
...
test_something(self):
with self.app.app_context():
# Do something
Is there a way to tell all of my tests to run using the current apps context to save me having this line in all of my tests?
I found the answer I was looking for by looking at the way the Flask-Testing extensions TestCase sets itself up, i.e., pushing the testing context to the _ctx stack inside a function that's called from within it's __call__ method.
class BaseTestCase(unittest2.TestCase):
def __call__(self, result=None):
try:
self._pre_setup()
super(BaseTestCase, self).__call__(result)
finally:
self._post_teardown()
def _pre_setup(self):
self.app = create_app()
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
def _post_teardown(self):
if getattr(self, '_ctx') and self._ctx is not None:
self._ctx.pop()
del self._ctx
And my test:
class SomeTestCase(BaseTestCase):
test_something(self):
# Test something - we're using the right app context here
You can try something like below.
DISCLAIMER: I just came up with the idea and didn't test this solution thoroughly although it seems to work. It is also IMHO rather ugly.
from functools import wraps
def with_context(test):
#wraps(test)
def _with_context(self):
with self.app.app_context():
test(self)
return _with_context
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
...
#with_context
test_something(self):
# Do something
Depending on how you test you may be able to use the test client. Example:
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
self.client = self.app.text_client()
test_something(self):
response = self.client.get('/something')
# check response

Categories

Resources