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',
)
Related
I'm trying to test my Flask APP, but constants which are set inside the Config class using environment variables (os.environ.get()), aren't overruled by monkeypatching.
My config.py:
from os import environ, path
from dotenv import load_dotenv
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))
class Config:
"""
Set Flask configuration from environment variables, if present.
"""
# General Config
MY_VARIABLE = environ.get("SOME_ENV_VARIABLE", "somedefault")
My __init__.py
from flask import Flask
from config import Config
def create_app():
app = Flask(__name__, instance_relative_config=False)
app.config.from_object(Config())
My tests/conftest.py
import pytest
from application import create_app
#pytest.fixture(autouse=True)
def env_setup(monkeypatch):
monkeypatch.setenv("SOME_ENV_VARIABLE", "test")
#pytest.fixture()
def app():
app = create_app()
# other setup can go here
yield app
# clean up / reset resources here
My tests/test_config.py:
class TestConfig:
def test_config_values(self, app):
assert app.config.get("MY_VARIABLE") == "test"
I keep getting AssertionError: assert 'somedefault' == 'test'
If I add a method to the config class with a #property decorator, as described at https://flask.palletsprojects.com/en/2.2.x/config/#development-production, then everything seems to work ok, for that specific property. But for class constants, it doesn't.
Any thoughts?
There are global variables which are instantiated from the environment at import time. The fixtures are applied after loading the test modules, so they kick in too late.
In other words, this is executed before:
MY_VARIABLE = environ.get("SOME_ENV_VARIABLE", "somedefault")
This is executed after:
monkeypatch.setenv("SOME_ENV_VARIABLE", "test")
There are several way to go around that:
don't execute environ.get at import time
monkeypatch MY_VARIABLE instead of "SOME_ENV_VARIABLE"
mock environment in pytest_sessionstart (but don't import the application from conftest.py, or it will still be too late)
Good day! Please tell me how you can solve the following problem in Python + FastAPI.
There is a test project:
app / main.py - main file
app / routes / users.py -set of api methods
app / repos / factory.py - repository factory
app / repos / user_repository.py - repositories
app / handlers / factory.py - handler factory
app / handlers / users.py - handlers
app / domain / user.py - data class
The main and routes structure is the same as in the example https://fastapi.tiangolo.com/tutorial/bigger-applications/
In the routes/users.py file:
from fastapi import APIRouter, Depends
from ..handlers import factory
router = APIRouter()
#router.get("/users/", tags=["users"])
def read_users(handler=Depends(factory.get_handler)):
return handler.get_all()
In the handlers/factory.py:
from fastapi import Depends
from .users import UserHandler1
def get_handler(handler=Depends(UserHandler1)):
return handler
In the handlers/users.py:
from fastapi import Depends
from ..repos import factory
class UserHandler1:
def __init__(self):
pass
def get_all(self, repo=Depends(factory.get_repo)):
return repo.get_all()
repos/factory.py:
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
return repo
repos/user_repository.py:
from ..domain.user import User
class UserRepository:
def __init__(self):
pass
def get_all(self):
return [User(1, 'A'), User(2, 'B'), User(3, 'C')]
domain/user.py:
class User:
id: int
name: str
def __init__(self, id, name):
self.id = id
self.name = name
Then I run hypercorn server: app.main:app --reload
Try call api method: http://127.0.0.1:8000/users/
And get the error AttributeError: 'Depends' object has no attribute 'get_all'
If you remove the handlers layer and do this, then everything will work.
routes/users.py:
from fastapi import APIRouter, Depends
from ..repos import factory
router = APIRouter()
#router.get("/users/", tags=["users"])
def read_users(repo=Depends(factory.get_repo)):
return repo.get_all()
It also works if you completely remove all Depends and create
UserRepository and UserHandler1 directly in factories.
Question 1: How do I use "Depends" in this case and why doesn't it work?
In general, the factory does not look like a good solution to this problem. I saw an example of DI implementation using multiple inheritance but as for me it is the same as factory method.
I also tried to use the Pinject library, but it requires the initial construction of a graph, which needs to be saved somewhere in order to access it in api handlers.
Question 2 (more important): How Dependency Injection can be applied in this case ?
The __call__ method must be implemented in the class.
As noted in the comments, a dependency can be anything that is a callable and thus a class as well. The only caveat in the latter case is that the class will only be initialized (i.e. only the __init__(...) function will be called).
So, in order to have a class as dependency, as in the example of https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcut you just need to call the target functions within the init and set the values as attributes of the class.
from ..domain.user import User
class UserRepository:
def __init__(self):
self.get_all()
def get_all(self):
self.users = [User(1, 'A'), User(2, 'B'), User(3, 'C')]
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
print(repo.users) # This will print the list of users
return repo
QUESTION 2
NB
This is a modelling question. Here I propose what I believe is
suitable from my point of view. It does not necessarily have to
be best or simplest approach.
Answering your second question, I would not advice for such complex dependencies. If the dependencies are at the router level, you can simply add them to the router, using the parameter depends=[...] and providing a list of dependency classes/functions.
Alternatively, you could declare all of the dependencies as function parameters of the endpoint, as you did for the factory. This method may lead to big chunks of code getting copied and pasted, so I advise for the above approach.
If you need to process the data parameters, then you add them to the request and access them from within the endpoint. See FastAPI get user ID from API key for a minimal example.
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()
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.
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()