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
Related
How can I use a pytest fixture within a TestCase method? Several answers to similar questions seem to imply that my example should work:
import pytest
from django.test import TestCase
from myapp.models import Category
pytestmark = pytest.mark.django_db
#pytest.fixture
def category():
return Category.objects.create()
class MyappTests(TestCase):
def test1(self, category):
assert isinstance(category, Category)
But this always results in an error:
TypeError: test1() missing 1 required positional argument: 'category'
I realize I could just convert this trivial example into a function, and it would work. I would prefer to use django's TestCase because it includes support for importing traditional "django fixture" files, which several of my tests require. Converting my tests to functions would require re-implementing this logic, since there isn't a documented way of importing "django fixtures" with pytest (or pytest-django).
package versions:
Django==3.1.2
pytest==6.1.1
pytest-django==4.1.0
I find it easier to use the "usefixtures" approach. It doesn't show a magical 2nd argument to the function and it explicitly marks the class for having fixtures.
#pytest.mark.usefixtures("category")
class CategoryTest(TestCase):
def test1(self):
assert Category.objects.count() == 1
I opted to rewrite django's fixture logic using a "pytest fixture" that is applied at the session scope. All you need is a single fixture in a conftest.py file at the root of your test directory:
import pytest
from django.core.management import call_command
#pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
fixtures = [
'myapp/channel',
'myapp/country',
...
]
with django_db_blocker.unblock():
call_command('loaddata', *fixtures)
This allowed me to throw out the class-based tests altogether, and just use function-based tests.
docs
Why do you need TestCase? I usually use Python class and create tests there.
Example
import pytest
from django.urls import reverse
from rest_framework import status
from store.models import Book
from store.serializers import BooksSerializer
#pytest.fixture
def test_data():
"""Поднимает временные данные."""
Book.objects.create(name='Book1', price=4000)
#pytest.fixture
def api_client():
"""Возвращает APIClient для создания запросов."""
from rest_framework.test import APIClient
return APIClient()
#pytest.mark.django_db
class TestBooks:
#pytest.mark.usefixtures("test_data")
def test_get_books_list(self, api_client):
"""GET запрос к списку книг."""
url = reverse('book-list')
excepted_data = BooksSerializer(Book.objects.all(), many=True).data
response = api_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.data == excepted_data
assert response.data[0]['name'] == Book.objects.first().name
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.
To test a polling app that I made using django, the pre-requisite for voting/viewing_results is that the user should be logged in. I wanted to create a testsuite where setup involves creating testuser, logging him in and teardown involves logging out the user and deleting the user.
I came across the setUp() and tearDown() methods - but got to know that they are done for each method in the test. I wanted to have this functionality at the class level and saw that django has setUpClass and tearDownClass - but as they are class methods I cannot do self.client.login , self.client.logout on them. I then created a LoginMixin which has setUp and tearDown methods.
I wanted to know which of these 2 methods is more pythonic and is there a better alternative rather than logging in and out before every test?
Have setUp, tearDown methods:
def setUp(self):
self.user = utils.create_user()
self.client.login(username='testuser', password='testpasswd')
def tearDown(self):
self.client.logout()
utils.delete_user(self.user)
Have a LoginMixin and let each test class derive from it:
class LoginMixin():
def setUp(self):
self.user = create_user()
self.client.login(username='testuser', password='testpasswd')
def tearDown(self):
self.client.logout()
class MyTest(LoginMixin, TestCase):
....
....
I am using django_nose as the TestRunner.
If the only reason you're not using the setUpClass classmethod is because you don't have access to the default Client, you could just create your own and refer to that:
from django.test.client import Client
class LoginMixin():
client = Client()
# ...
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()