Сопряжение исключений psycopg2 в модульных тестах Django
Я изо всех сил пытаюсь написать модульные тесты на Django для конкретных psycopg2 ошибок, которые в конечном итоге вызывают django.db.IntegrityError в качестве конечного результата.
Обычно я использую mock.patch и устанавливаю side_effect на исключение, которое я хотел бы поднять.
Ex.
with mock.patch(
"path_to.method_that_throws_integrity_error",
side_effect=IntegrityError(),
) as mock_method:
self.assertEqual(value, value_two)
Это отлично работает, если бы я заботился о следующих шагах после каждого IntegrityError.
Однако, в случае с этим тестом. Я забочусь только о логике в моем коде, которая следует за psycopg2.errors.UniqueViolation, которая в конечном итоге всплывает и бросает IntegrityError, которую я проверяю error.__cause__.diag.constraint_name и обрабатываю логику на основе результата.
Если выбрасывается UniqueViolation, у меня есть пользовательская логика, которая в настоящее время выполняет действие. Если будет брошена ошибка IntegrityError, которая не является UniqueViolation, я хочу, чтобы ошибка поднималась, чтобы я был предупрежден о наличии проблемы.
Я перепробовал много вещей, и не могу поднять UniqueViolation так, чтобы он устанавливал тот же объект psycopg2.extensions.Diagnostics, что и тот, который я получаю от фактического выброса ошибки, нарушая уникальное ограничение в моей Db. Я также не могу установить __cause__ на IntegrityError, как UniqueViolation.
Что я хотел бы получить, так это что-то вроде этого -
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)
Вышеописанное позволит мне вызвать функцию базы данных, поднять уникальное исключение и проверить в юнит-тесте, что была вызвана соответствующая логика. Я знаю, что логика работает, потому что я могу поднять исключение при истинном нарушении уникального ограничения, но мне нужно покрытие.
Спасибо за помощь.
Я не уверен в правильности этого, но вы можете просто использовать обезьяний патч, чтобы установить атрибут constraint_name в exception и использовать имя функции без вызова самой функции. Я имею в виду что-то вроде этого:
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)
Это должно сработатью
Upd. Или лучше просто создать фиктивный класс исключений для теста. Это поможет избежать "обезьяньего" патча:
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
И, возможно, вам стоит использовать psycopg2.errorcodes, если вы этого не делаете: там есть много специфических для postgresql кодов, которые помогают избежать магических чисел и строк