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.