Pytest: monkeypatch not overruling environ variable - python

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)

Related

Cant inherit TestCase from another file

im using the unittest Framework to test my flask application. Since i have multiple Testcase classes i want to structure/refactor them.
BaseTest.py contains:
import unittest
from config import Config
from app import create_app, db
class TestConfig(Config):
""" overridden config for testing """
class TestInit(unittest.TestCase):
def setUp(self):
self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
self.app = self.app.test_client()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
Then i try to have testcases in
ProjectTest.py:
from app.models import *
from tests.BaseTest import TestInit
class ProjectTest(TestInit):
def setUp(self):
super().setUp()
# create Test Data
proj1 = Project(
name='TestProject1',
project_state_id=1,
project_type_id=1
)
db.session.add(proj1)
db.session.commit()
for pro in Project.query.all():
print(pro)
def test_project_add(self):
pass
i get the error message:
ModuleNotFoundError: No module named 'tests.BaseTest'; 'tests' is not a package
I have all theses files in the folder tests and tried all variation on how to import it (even with a __init__.py file) but i always get the error.
if your source codes are in the same folder, you don't need to import tests.BaseTest
because interpreter shouldn't look anywhere else except current folder.
just importing BaseTest would be enough.
edited code would be:
from BaseTest import TestInit

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()

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',
)

Unit testing a Flask application class

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()

Categories

Resources