Сопряжение исключений 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 кодов, которые помогают избежать магических чисел и строк

Вернуться на верх