Pytest and Django, proper method for creating module scoped fixtures - python

pytest module scoped fixtures that use the django test database are perplexing us. We want a fixture, that creates an entry in the database, once per test module.
This works, but it's very clunky:
import pytest
#pytest.fixture(scope='module')
def site(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
address = models.Address.create(..)
site = models.Site.create(address=address, ..)
yield site
with django_db_blocker.unblock():
site.delete()
address.delete()
We've noticed that django_db_setup is required, because if this fixture is the first one called, the test database won't be set up and the fixture will be created in the non-test database!
For complicated objects that have more than one or two related models, the fixture becomes very ugly.
Is there a better way to do this?

Related

Django test with fixtures gives ForeignKeyViolation or IntegrityError

I am trying to write test cases for the Django RestAPI that we have but I have an issue with the fixtures loading. Everything works correctly when I have only one TestCase but when I add a second TestCase in a second django app I get django.db.utils.IntegrityError. My original intention was to create a general TestCase where I set up the most used objects in the setUpTestData function and make the other tests inherit from that one.
Things I have tried:
Using APITestCase from rest_framework and TestCase from django
Not using inheritance, and having both files using TestCase from django
Using setUp method instead of setUpTestData
Using call_command from django.core.management in the setUpTestData method instead of creating the fixtures in each class in the class variable
Declaring the fixtures only in the TestCase that is executed first (this makes the other TestCase have an empty DB, so it it clear that for each TestCase info is recreated)
Changed the order of the files in the fixtures variable
When I comment one of the test files the other works and vice-versa. When I used the call_command with verbose=2, the fixtures in the first file executed work perfectly, and it breaks just when trying to install the first fixture of the second file, with the error:
django.db.utils.IntegrityError: Problem installing fixtures: insert or update on table "preference_questions" violates foreign key constraint "preference_questi_preference_questi_64f61c66_fk_prefer" DETAIL: Key (preference_question_category_id)=(2) is not present in table "preference_question_category"
Sometimes it gives ForeignKeyViolation depending on the case of the above mentioned.
Turns out the issue was that the fixtures had IDs on them, those IDs cannot be removed because objects had relations between them. In this case, there are two options I found:
Use natural keys as explained in this post, this makes you create new methods for the natural keys and dump your DB
Create the objects manually in the setUpTestData (labor intensive but won't give you any problems)
Depending on your case it may be more interesting to use one or the other. Nonetheless, the first one is the best probably.

What are the different use cases for APITestCase, APISImpleTestCase, and APITransactionTestCase in Django Rest Framework

The docs for the different test case classes are here
I am unsure of what situations I would use each of the test case classes:
APITestCase
APISimpleTestCase
APITransactionTestCase
As explained in the Django Rest Framework Docs, the 3 available test classes simply extend the regular Django test classes but switch the client to use APIClient.
This can also be seen in the Django Rest Framework source code
class APITransactionTestCase(testcases.TransactionTestCase):
client_class = APIClient
class APITestCase(testcases.TestCase):
client_class = APIClient
class APISimpleTestCase(testcases.SimpleTestCase):
client_class = APIClient
The first test case you should know about is the APISimpleTestCase which allows us to test general DRF/Django things such as http redirects and checking some callable raises an exception. The docs note that we shouldn't use APISimpleTestCase when doing any testing with the database.
The reason we shouldn't use APISimpleTestCase with the database is because the test data would stay in the database across multiple tests. To get around this we must use APITransactionTestCase which will use atomic() blocks to wrap tests in transactions and allow the test runner to roll back the database at the beginning of each test, allowing easy atomic testing of database related actions. It also adds some extra assertion methods related to database assertions such as assertNumQueries.
Finally, the APITestCase wraps the tests with 2 atomic() blocks, one for the whole test class and one for each test within the class. This essentially stops tests from altering the database for other tests as the transactions are rolled back at the end of each test. By having this second atomic() block around the whole test class, specific database transaction behaviour can be hard to test and hence you'd want to drop back to using APITransactionTestCase.

Django DB records disappears after first testcase run

I'm working on a project based on Django REST Framework. So I need to write some test cases for my REST API.
I've written some basic class (let's call it BaseAPITestCase) inherited from standard DRFs APITransactionTestCase.
In this class I've defined setUp method where I'm creating some test user which belongs to some groups (I'm using UserFactory written with FactoryBoy).
When I run my tests, the first one (first test case method from first child class) successfully creates a user with specified groups, but the others don't (other test case methods in the same class).
User groups just don't exist in DB at this time. It seems like existed records are deleted from DB at each new test case run. But how then it works for the first time?
I've read Django test documentation but can't figure out why it happens... Can anyone explain it?
The main question is what I should do to make these tests works?
Should I create user once and store it in object variable?
Should I add some params to preserve user groups data?
Or should I add user groups to fixtures? In that case, how can I create this fixture properly? (All related models, such as permissions and content types)
Simplified source code for illustration:
from rest_framework.test import APITransactionTestCase
class BaseAPITestCase(APITransactionTestCase):
def setUp(self):
self.user = UserFactory(
username='login',
password='pass',
group_names=('admin', )
)
self.client = APIClient()
self.client.force_login(self.user)
def tearDown(self):
self.client.logout()
class CampaignListTest(BaseAPITestCase):
def test_authorized_get(self):
# successfully gets user groups from DB
def test_authorized_post(self):
# couldn't find any groups
TransactionTestCase is a test case to test transactions. As such, it explicitly does not use transactions to isolate tests, as that would interfere with the behaviour of the transactions that are being tested.
To isolate tests, TransactionTestCase rolls back the database by truncating all tables. This is the easiest and fastest solution without using transactions, but as you noticed this will delete all data, including the groups that were generated in a post_migrate signal receiver. You can set serialized_rollback = True on the class, in which case it will serialize all changes to the database, and reverse these changes them after each test. However, this is significantly slower, and often greatly increases the time it takes to run the test suite, so this is not the default.
TestCase does not have this restriction, so it wraps each test case in a transaction, and each individual test in a savepoint. Roll back using transactions and savepoints is fast and allows you to keep the data that was there at the start of the transaction or savepoint. For this reason, it is preferable to use TestCase whenever possible.
This extends to DRF's APITransactionTestCase and APITestCase, which simply inherit from Django's test cases.

Run Django unit tests from custom management command

I used to have a standalone script with some unit tests to test data in our database. I did not use the builtin Django testing tool, as that would create an empty testing database, which is not what I want.
In that script, I created three different classes extending unittest.TestCase containing some test functions that directly executed SQL statements.
Now I would prefer to be able to access the Django ORM directly. The easiest way to do this is via a custom management commant (./manage.py datatests).
In the standalone script, I could call all unit tests via the following function:
if __name__ == '__main__':
unittest.main()
It would discover all tests in the current file and run them.
How can I do an equivalent thing (run some test suites) from within a custom Django management command?
I'm sorry for not having searched for an answer long enough before asking, but I found the solution to this problem myself in another Stackoverflow answer:
How to run django unit-tests on production database?
Essentially, instead of unittest.main() the following code can be used:
suite = unittest.TestLoader().loadTestsFromTestCase(TestCaseClass)
unittest.TextTestRunner(verbosity=2).run(suite)
This will load all tests in the specified TestCaseClass. If you want to load all tests in the current module, creating the suite this way will help:
suite = TestLoader().loadTestsFromName(__name__)
The Stackoverflow answer linked above contains a full example. Furthermore, the Basic Example section of the unittest module docs describes the same thing. For other options to load tests, see Loading and running tests in the docs.
You may want to specify the contents of your start-up db through fixtures. It will load up the context for db for particular test. And you can take a snapshot of db with
$ ./manage.py dumpdata my_app > fixtures/my_pre_test_db.json`
Now in your unit test you will have something like this:
class MyTestCase(TestCase):
fixtures = ['fixtures/my_pre_test_db.json']
def testThisFeature(self):
...

django unit testing and global fixtures

I'm working on a web project in Django, and I'm using the python unittest framework. For every app I have some fixtures. This means, that every app has some identical tables in fixtures. I would like to share fixtures between apps and testcases, because otherwise if I change a model, I will have to change all json fixtures where this concrete table is referenced.
Is it sensible to use global fixtures?
Do not use static fixtures, it is a bad automated tests pattern. Use dynamic fixtures.
Django Dynamic Fixture has options to create global fixtures. Check its Nose plugin or the Shelve option.
I would highly recommend looking into Django's Testing architecture. Check out TestCase.fixtures especially; this is far more advanced and Django-specific than unittest.
I can't think of anything wrong with using global fixtures as long as you delete them in your tearDown method (or teardown_test_environment method - see below).
I am not sure if you are asking to find out how to do this. If so there are two ways that I can think of.
Use a common base class for all your tests. Something like this:
class TestBase(django.test.TestCase):
fixtures = ['common_fixtures.xml']
class MyTestClass(TestBase):
fixtures = TestBase.fixtures + ['fixtures_for_this_test.xml']
def test_foo(self):
# test stuff
Use a custom test runner. In your test runner load all the fixtures you need before running the tests and take them down after executing the tests. You should preferably do this by using your own setup_ and teardown_test_environment methods.

Categories