7đź‘Ť
I’ve been running into this issue sporadically for months now. I believe I just figured out the root cause and a couple solutions.
Summary
For whatever reason, it seems like the Django test case base classes aren’t removing the database records created by let’s just call it TestCase1
before running TestCase2
. Which, in TestCase2
when it tries to create records in the database using the same IDs as TestCase1
the database raises a DuplicateKey
exception because those IDs already exists in the database. And even saying the magic word “please” won’t help with database duplicate key errors.
Good news is, there are multiple ways to solve this problem! Here are a couple…
Solution 1
Make sure if you are overriding the class method tearDownClass
that you call super().tearDownClass()
. If you override tearDownClass()
without calling its super, it will in turn never call TransactionTestCase._post_teardown()
nor TransactionTestCase._fixture_teardown()
. Quoting from the doc string in TransactionTestCase._post_teardown()`:
def _post_teardown(self):
"""
Perform post-test things:
* Flush the contents of the database to leave a clean slate. If the
class has an 'available_apps' attribute, don't fire post_migrate.
* Force-close the connection so the next test gets a clean cursor.
"""
If TestCase.tearDownClass()
is not called via super()
then the database is not reset in between test cases and you will get the dreaded duplicate key exception.
Solution 2
Override TransactionTestCase
and set the class variable serialized_rollback = True
, like this:
class MyTestCase(TransactionTestCase):
fixtures = ['test-data.json']
serialized_rollback = True
def test_name_goes_here(self):
pass
Quoting from the source:
class TransactionTestCase(SimpleTestCase):
...
# If transactions aren't available, Django will serialize the database
# contents into a fixture during setup and flush and reload them
# during teardown (as flush does not restore data from migrations).
# This can be slow; this flag allows enabling on a per-case basis.
serialized_rollback = False
When serialized_rollback
is set to True
, Django test runner rolls back any transactions inserted into the database beween test cases. And batta bing, batta bang… no more duplicate key errors!
Conclusion
There are probably many more ways to implement a solution for the OP’s issue, but these two should work nicely. Would definitely love to have more solutions added by others for clarity sake and a deeper understanding of the underlying Django test case base classes. Phew, say that last line real fast three times and you could win a pony!
7đź‘Ť
I continued to get this error without having any duplicate data but I was able to resolve the issue by initializing the object and calling the save()
method rather than creating the object via Model.objects.create()
In other words, I did this:
@classmethod
def setUpTestData(cls):
cls.person = Person(first_name="Jane", last_name="Doe")
cls.person.save()
Instead of this:
@classmethod
def setUpTestData(cls):
cls.person = Person.objects.create(first_name="Jane", last_name="Doe")
1đź‘Ť
The log you provided states DETAIL: Key (product_name)=(Almonds) already exists
. Did you verify in your db?
To prevent such errors in the future, you should prefix all your test data string by test_
0đź‘Ť
I discovered the issue, as noted at the bottom of the question.
From what I can tell, the database didn’t like me using duplicate data in the setUpTestData()
methods of two different tests. Changing the primary key values in the second test corrected the problem.
- Moving django apps into subfolder and url.py error
- Setting DJANGO_SETTINGS_MODULE under virtualenv?
- Django Form DateInput with widget in update: loosing the initial value
0đź‘Ť
I think the problem here is that you had a tearDownClass method in your TestCase without the call to super method.
In this way the django TestCase lost the transactional functionalities behind the setUpTestData so it doesn’t clean your test db after a TestCase is finished.
Check warning in django docs here:
https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.SimpleTestCase.allow_database_queries
- Python: assert if string matches a format
- IPython doesn't work in Django shell
- X-editable inline editing in Django – how to get CSRF protection?
0đź‘Ť
I had similar problem that had been caused by providing the primary key value to a test case explicitly.
As discussed in the Django documentation, manually assigning a value to an auto-incrementing field doesn’t update the field’s sequence, which might later cause a conflict.
I have solved it by altering the sequence manually:
from django.db import connection
class MyTestCase(TestCase):
@classmethod
def setUpTestData(cls):
Model.objects.create(id=1)
with connection.cursor() as c:
c.execute(
"""
ALTER SEQUENCE "app_model_id_seq" RESTART WITH 2;
"""
)