简体   繁体   中英

Mocking psycopg2 Exceptions in Django Unit Tests

I'm struggling to write unit tests in Django for specific psycopg2 errors that ultimately raise django.db.IntegrityError as the end result.

Typically I'd use mock.patch and have the side_effect set to the exception I would like raised.

Ex.

with mock.patch(
    "path_to.method_that_throws_integrity_error",
        side_effect=IntegrityError(),
    ) as mock_method:
        self.assertEqual(value, value_two)

This works great if I cared about the next steps after every IntegrityError .

However, in the case of this test. I only care about the logic in my code that follows psycopg2.errors.UniqueViolation which eventually bubbles up and throws an IntegrityError which I check the error.__cause__.diag.constraint_name and handle logic based on the result.

If the UniqueViolation is thrown, I have custom logic that currently performs an action. If an IntegrityError is thrown that is not a UniqueViolation I want the error to raise so I'm alerted that there is an issue.

I've tried many things, and cannot mock raise the UniqueViolation so that it sets the same psycopg2.extensions.Diagnostics object as the one that I get from actually throwing the error by violating the unique constraint in my Db. I also cannot set the __cause__ on the IntegrityError as the UniqueViolation .

What I would like is something like this -

def side_effect():
    try:
        raise UniqueViolation({"constraint_name": "my_unique_constraint"}) # not sure how to set the constraint name
    except UniqueViolation as e
        raise IntegrityError from e

with mock.patch(
    "path_to.method_that_throws_integrity_error",
        side_effect=side_effect(),
    ) as mock_method:
        self.assertEqual(value, value_two)

With the above, I'd be able to call my database function, raise the unique exception, and test in a Unit Test that the appropriate logic is called. I know the logic works because of being able to grab the exception in a true violation of the unique constraint, but I want coverage.

Thanks for the help.

I'm not sure about right way to do that but you can just use monkey patching to set constraint_name attribute to exception and use function name without calling itself. I mean something like that:

def side_effect():
    try:
        exception = UniqueViolation()
        exception.constraint_name = "my_unique_constraint"
        raise exception
    except UniqueViolation as e:
        raise IntegrityError from e

with mock.patch(
"path_to.method_that_throws_integrity_error",
    side_effect=side_effect,
) as mock_method:
    self.assertEqual(value, value_two)

This should workю

Upd Or better you can just create dummy exceptions class for the test. This can help avoid monkey patching:

def side_effect():
    class DummyException(Exception):
        def __init__(self, *args, **kwargs):
            super(PGError, self).__init__(*args, **kwargs)
            self.constraint_name = "my_unique_constraint"

    try:
        raise DummyException
    except UniqueViolation as e:
        raise IntegrityError from e

And maybe you should use psycopg2.errorcodes if you don't: there are many postgresql-specific codes that help to avoid magic numbers and strings

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM