1

I have written a test which checks whether IntegrityError was raised in case of duplicate records in the database. To create that scenario I am issue a REST API twice. The code looks like this:

class TestPost(APITestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        common.add_users()

    def tearDown(self):
        super().tearDown()
        self.client.logout()

    def test_duplicate_record(self):
        # first time
        response = self.client.post('/api/v1/trees/', dict(alias="some name", path="some path"))
        # same request second time
        response = self.client.post('/api/v1/trees/', dict(alias="some name", path="some path"))
        self.assertEqual(response.status_code, status.HTTP_400_BAD_RREQUEST)

But I get an error stack like this

 "An error occurred in the current transaction. You can't "
django.db.transaction.TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.

How can I avoid this error this is certainly undesirable.

1
  • Please show the full traceback. Commented May 18, 2016 at 13:59

2 Answers 2

3

I've come across this issue today, and it took me a while to see the bigger picture, and fix it properly. It's true that removing self.client.logout() fixes the issue, and is probably not needed there, but the problem lies in your view.

Tests which are subclasses of TestCase wrap your test cases and tests in db transactions (atomic blocks), and your view somehow breaks that transaction. For me it was swallowing an IntegrityError exception. At that point, the transaction was already broken, but Django didn't know about it, so it couldn't correctly perform a rollback. Any query that would then be executed would cause a TransactionManagementError.

The fix for me was to correctly wrap the view code in yet another atomic block:

try:
    with transaction.atomic():  # savepoint which will be rolled back
        counter.save()  # the operation which is expected to throw an IntegrityError
except IntegrityError:
    pass  # swallow the exception without breaking the transaction

This may not be a problem for you in production, if you're not using ATOMIC_REQUESTS, but I still think it's the correct solution.

Sign up to request clarification or add additional context in comments.

2 Comments

What is it about ATOMIC_REQUESTS that would make this problematic in production?
@AndrewKonoff ATOMIC_REQUESTS wrap the whole view in a transaction, which might be really useful when a crash occurs in the middle of the views' execution. But it then also creates the same scenario as in the tests.
1

Try removing self.client.logout() from the tearDown method. Django rolls back the transaction at the end of each test. You shouldn't have to log out manually.

2 Comments

wow ! the problem has resolved after removing the logout. Could you please help me understand, where "transaction" has come into picture at first place and why removing "logout" actually resolved it. I think test.APITestCase is doing much more than what I think.
An error occurred in one of your tests. You then called self.client.logout() in the teardown method. This isn't possible, because it's not possible to execute queries in the transaction after there has been an error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.