py.test and Django DB access except for one test class - django

To me it's clear "How can I give database access to all my tests without the django_db marker?"
But I would prefer/need to have several class tests without the DB access.
How can I exclude classes or methods when enable_db_access_for_all_tests is active for all tests?
Is there a decorator like #pytest.mark.no_django_db or other possible solutions?
Thanks!
D

The most flexible solution for marking your tests would be to use the pytest_collection_modifyitems hook in your conftest.py and selectively add a marker for those tests where you need db access. This is an example that traverses all the collected tests and add a marker to them.
def pytest_collection_modifyitems(config, items):
# Do some filtering to items
for item in items:
item.add_marker('django_db')
It's safe to use a import pdb; pdb.set_trace() or any other debugging tool at your disposal to check what item looks like.

Related

Skipping Django test database creation for read-only, externally managed, high-security, big databases

We need to use real data for one of our many external data sources during Django tests:
data is externally managed and read-only
data is accessed through manage.py inspectdb generated ORM classes
data is highly sensitive and we are not allowed to store fixtures of the actual data
tables are legacy design and will be phased out, hundreds of tables with complex relations, even getting a single record is complex
there is too much to do and I am unwilling to spend the time it would take to generate the fixtures, guarantee they're obfuscated, get approval of the obfuscation and justify keeping it around just to bridge us for a few months
We understand the downsides: This violates test purity and introduces a potential safety risk. We are willing to compromise on both to get us past the next few months when we will phase out this problem data source.
In this case, I need Django to understand that I don't want it to stand up a test database for this source, and just use the actual source so we can run some quick checks and walk away.
What is the simplest way to achieve this, with full understanding and acceptance of the risks and recommendations against?
For us, the solution was a custom test runner.
With help from Django's Advanced Testing Topics documentation, we overrode the default DiscoverRunner like this:
from django.test.runner import DiscoverRunner
def should_create_db(db_name):
# analyse db_name, a key from DATABASES, to determine whether a test
# database should be created
return db_name != 'messy_legacy_database'
class CustomTestRunner(DiscoverRunner):
# override method from superclass to selectively skip database setup
def setup_databases(self, **kwargs):
# 'aliases' is a set of unique keys from settings DATABASES dictionary
aliases = kwargs.get('aliases')
filtered = set([i for i in aliases if should_create_db(i)])
kwargs['aliases'] = filtered
# 'aliases' now contains only keys which trigger test database creation
return super().setup_databases(**kwargs)
# there was no need to override teardown_databases()
Next we update settings.py to use our override instead of the default runner:
TEST_RUNNER = 'path.to.CustomTestRunner'
Finally we tell our test class which databases it can use:
from django.test import TestCase
class OurTest(TestCase):
databases = [
'default',
'messy_legacy_database',
]
def test_messy_legacy_database(self):
# go nuts on your messy legacy database testing calls
pass
In this way our tests now skip test database creation for our messy legacy databases, and the logic we test pulls data from the actual data sources, allowing us to implement quick checks to ensure these code paths work.

Django unit testing: Separating unit tests without querying the database multiple times

I have a pair of tests like this:
Make sure a task's deletion status initializes as None
def test_initial_task_deletion_status_is_none(self):
unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
self.assertIsNone(unfinished_task.delete_status)
# Make sure a task's deletion status changes appropriately
def test_unfinished_task_deletion_status_updates_appropriately(self):
unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
unfinished_task.timed_delete(delta=.1)
self.assertIs(unfinished_task.delete_status, "Marked for Deletion")
This will go on, but I'll have unfinished_task = Task.objects.get(tasked_with="Unfinished Test") at the beginning of every one. Is there a way to split these types of things into separate tests, but use the same query result?
Assuming you're using Django's testing framework, then you can do this using setUp().
More about unittest.TestCase.setUp() here
So your updated snippet would look like:
from django.test import TestCase
class MyTestCase(TestCase):
def setUp(self):
self.unfinished_task = Task.objects.get(tasked_with="Unfinished Test")
def test_initial_task_deletion_status_is_none(self):
self.assertIsNone(self.unfinished_task.delete_status)
# Make sure a task's deletion status changes appropriately
def test_unfinished_task_deletion_status_updates_appropriately(self):
self.unfinished_task.timed_delete(delta=.1)
self.assertIs(self.unfinished_task.delete_status, "Marked for Deletion")
You can place the repeated line in the setUp method, and that will make your code less repetitive, but as DanielRoseman pointed out, it will still be run for each test, so you won't be using the same query result.
You can place it in the setUpTestData method, and it will be run only once, before all the tests in MyTestCase, but then your unfinished_task object will be a class variable, shared across all the tests. In-memory modifications made to the object during one test will carry over into subsequent tests, and that is not what you want.
In read-only tests, using setUpTestData is a good way to cut out unnecessary queries, but if you're going to be modifying the objects, you'll want to start fresh each time.

Django DRF APITestCase chain test cases

For example I want to write several tests cases like this
class Test(APITestCase):
def setUp(self):
....some payloads
def test_create_user(self):
....create the object using payload from setUp
def test_update_user(self):
....update the object created in above test case
In the example above, the test_update_user failed because let's say cannot find the user object. Therefore, for that test case to work, I have to create the user instead test_update_user again.
One possible solution, I found is to run create user in setUp. However, I would like to know if there is a way to chain test cases to run one after another without deleting the object created from previous test case.
Rest framework tests include helper classes that extend Django's existing test framework and improve support for making API requests.
Therefore all tests for DRF calls are executed with Django's built in test framework.
An important principle of unit-testing is that each test should be independent of all others. If in your case the code in test_create_user must come before test_update_user, then you could combine both into one test:
def test_create_and_update_user(self):
....create and update user
Tests in Django are executed in a parallell manner to minimize the time it takes to run all tests.
As you said above if you want to share code between tests one has to set it up in the setUp method
def setUp(self):
pass

Is there a standard way to mock Django models?

I have a model called Pdb:
class Pdb(models.Model):
id = models.TextField(primary_key=True)
title = models.TextField()
It is in a one-to-many relationship with the model Residue:
class Residue(models.Model):
id = models.TextField(primary_key=True)
name = models.TextField()
pdb = models.ForeignKey(Pdb)
Unit tesing Pdb is fine:
def test_can_create_pdb(self):
pdb = Pdb(pk="1XXY", title="The PDB Title")
pdb.save()
self.assertEqual(Pdb.objects.all().count(), 1)
retrieved_pdb = Pdb.objects.first()
self.assertEqual(retrieved_pdb, pdb)
When I unit test Residue I just want to use a mock Pdb object:
def test_can_create_residue(self):
pdb = Mock(Pdb)
residue = Residue(pk="1RRRA1", name="VAL", pdb=mock_pdb)
residue.save()
But this fails because it needs some attribute called _state:
AttributeError: Mock object has no attribute '_state'
So I keep adding mock attributes to make it look like a real model, but eventually I get:
django.db.utils.ConnectionDoesNotExist: The connection db doesn't exist
I don't know how to mock the actual call to the database. Is there a standard way to do this? I really don't want to have to actually create a Pdb record in the test database because then the test won't be isolated.
Is there an established best practices way to do this?
Most of the SF and google results I get for this relate to mocking particular methods of a model. Any help would be appreciated.
You are not strictly unit testing here as you are involving the database, I would call that integration testing, but that is another very heated debate!
My suggestion would be to have your wrapping test class inherit from django.test.TestCase. If you are that concerned about each individual test case being completely isolated then you can just create multiple classes with a test method per class.
It might also be worth reconsidering if these tests need writing at all, as they appear to just be validating that the framework is working.
Oh, I managed to solve this with a library called 'mixer'...
from mixer.backend.django import mixer
def test_can_create_residue(self):
mock_pdb = mixer.blend(Pdb)
residue = Residue(pk="1RRRA1", name="VAL", pdb=mock_pdb)
residue.save()
Still think django should provide a native way to do this though. It already provides a lot of testing tools - this feels like a major part of proper unit testing.
I'm not sure exactly what you mean by mocking Django models. The simplest option for writing a test that requires some model objects is to use a test fixture. It's basically a YAML file that gets loaded into a database table before your test runs.
In your answer you mentioned mixer, which looks like a library for randomly generating those test fixtures.
Those are fine tools, but they still require database access, and they're a lot slower than pure unit tests. If you want to completely mock out the database access, try Django mock queries. It completely mocks out the database access layer, so it's very fast, and you don't have to worry about foreign keys. I use it when I want to test some complex code that has simple database access. If the database access has some complicated query conditions, then I stick with the real database.
Full disclosure: I'm a minor contributor to the Django mock queries project.

independence of individual test methods in APITest class in Django Rest Framework

I followed the tutorial for testing from APITestCase documentation in dry site. But I could find answers to some of my doubts in the drf document.
I have a APITestCase subclassed as below
class GroupTest(APITestCase):
def setUp(self):
.
.
def tearDown(self):
.
.
def test_case_A(self):
.
# I create a group here
# but I dont delete the group object in case A
.
def test_case_B(self):
.
# Will the group object from case A exist in case B ?
# are the different test methods in a APITestCase independent?
.
If I have two test cases in GroupTest class, are they independent? will a group object created in case A affect case B?
No, each test will run on clean database. If you need some entities in DB - add them in setUp (they will be awailable across all test cases in class), or directly in test case.
After testcase execution, all changes are rolled back. If you have some other changes to be undone (for example, you create some files) - do this in tearDown.
Tests are good place for experiments. It's easy and fun to make some temporary tests to check some assumptions.
For example, to get the answer to your question, you can make 2 simple tests cases, each of them should to create some instance and to check if the instance created in other test exists (use print() commands to see what's going on).