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
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)
I'm trying to write a simple unit test to test an instance method of one of my models in Django. However my class initialises an external connection on __init__ which is not being patched even though I'm trying to target it.
Folder Structure:
- project/
- app1/
- tests/
- tests_models.py
- models.py
models.py:
from 3rdPartyPlugin import Bot
class MyClass:
def __init__(self, token):
self.bot = Bot(token)
def generate_buttons(self):
...
tests_models.py:
from django.test import TestCase
from unittest.mock import MagicMock, patch
from app1.models import MyClass
#patch('app1.models.3rdPartyPlugin.Bot')
class GenerateButtonsTestCase(TestCase):
def setUp(self):
self.tb = MyClass('', '', '')
def test_generates_button_correctly(self):
return True
I can't get past the setUp step because the initialisation of the class fails because it tries to reach out to that 3rdPartyPlugin module even though I patched it.
I've tried setting the patch to:
#patch('app1.models.3rdPartyPlugin.Bot')
#patch('app1.models.Bot')
#patch('app1.models.TB.Bot')
But all of the above still leads to the Bot being called. Any suggestions?
The problem is that Bot is already imported and read in models.py before you patch it.
Try to import the whole module instead:
import 3rdPartyPlugin
class MyClass:
def __init__(self, token):
self.bot = 3rdPartyPlugin.Bot(token)
def generate_buttons(self):
I eventually was able to solve this by moving the 3rdPartyPlugin code out of the initialiser and into an instance method on the class I was testing. This is how it looked in code:
models.py:
from 3rdPartyPlugin import Bot
class MyClass:
def __init__(self, token):
self.token = token
self.bot = self.bot()
def bot(self):
Bot(self.token)
tests_models.py:
from app1.models import MyClass
class GenerateButtonsTestCase(TestCase):
#patch('app1.models.MyClass.bot')
def setUp(self, _):
self.tb = MyClass('', '', '')
And after making the above changes, the patch correctly works and patches my use of Bot from the 3rdPartyPlugin. It's a bit messy but it works for me.
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 am using Django 1.4's LiveServerTestCase for Selenium testing and am having trouble with the setUpClass class method. As far as I understand MembershipTests.setUpClass is run once before the unit tests are run.
I've put code to add a user to the database in MembershipTests.setUpClass but when I run the MembershipTests.test_signup test no user has been added to the test database. What am I doing incorrectly? I expect the user I created in setUpClass would be available across all unit tests.
If I put the user creation code in MembershipTests.setUp and run MembershipTests.test_signup I can see the user, but don't want this run before every unit test as setUp is. As you can see, I use a custom LiveServerTestCase class to add basic functionality across all of my tests (test_utils.CustomLiveTestCase). I suspect this has something to do with my issue.
Thanks in advance.
test_utils.py:
from selenium.webdriver.firefox.webdriver import WebDriver
from django.test import LiveServerTestCase
class CustomLiveTestCase(LiveServerTestCase):
#classmethod
def setUpClass(cls):
cls.wd = WebDriver()
super(CustomLiveTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
cls.wd.quit()
super(CustomLiveTestCase, cls).tearDownClass()
tests.py:
from django.contrib.auth.models import User
from django.test.utils import override_settings
from test_utils import CustomLiveTestCase
from test_constants import *
#override_settings(STRIPE_SECRET_KEY='xxx', STRIPE_PUBLISHABLE_KEY='xxx')
class MembershipTests(CustomLiveTestCase):
fixtures = [
'account_extras/fixtures/test_socialapp_data.json',
'membership/fixtures/basic/plan.json',
]
def setUp(self):
pass
#classmethod
def setUpClass(cls):
super(MembershipTests, cls).setUpClass()
user = User.objects.create_user(
TEST_USER_USERNAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD
)
def test_signup(self):
print "users: ", User.objects.all()
The database is torn down and reloaded on every test method, not on the test class. So your user will be lost each time. Do that in setUp not setUpClass.
Since you're using LiveServerTestCase it's almost same as TransactionTestCase which creates and destroys database (truncates tables) for every testcase ran.
So you really can't do global data with LiveServerTestCase.
You should be able to use TestCase.setUpTestData as follows (slight changes to your base class):
test_utils.py:
from selenium.webdriver.firefox.webdriver import WebDriver
from django.test import LiveServerTestCase, TestCase
class CustomLiveTestCase(LiveServerTestCase, TestCase):
#classmethod
def setUpClass(cls):
cls.wd = WebDriver()
super(CustomLiveTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
cls.wd.quit()
super(CustomLiveTestCase, cls).tearDownClass()
tests.py:
from django.contrib.auth.models import User
from django.test.utils import override_settings
from test_utils import CustomLiveTestCase
from test_constants import *
#override_settings(STRIPE_SECRET_KEY='xxx', STRIPE_PUBLISHABLE_KEY='xxx')
class MembershipTests(CustomLiveTestCase):
fixtures = [
'account_extras/fixtures/test_socialapp_data.json',
'membership/fixtures/basic/plan.json',
]
#classmethod
def setUpTestData(cls):
super(MembershipTests, cls).setUpTestData()
user = User.objects.create_user(
TEST_USER_USERNAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD
)
def test_signup(self):
print "users: ", User.objects.all()
Instead of changing the base class, you could inherit from TestCase in MembershipTests, but you'll have to do this everytime you need test data.
Note that I've also removed the def setUp: pass, as this will break the transaction handling.
Check out this thread for further details: https://groups.google.com/forum/#!topic/django-developers/sr3gnsc8gig
Let me know if you run into any issues with this solution!
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()