Я пытаюсь понять, как работает исправление, и тестирую с помощью pytest a Django View:
views.py
from django.contrib.auth.views import LoginView
class MyLoginView(LoginView):
pass
test_view.py
from django.test import RequestFactory
from .views import MyLoginView
rf = RequestFactory()
def test_login(rf):
request = rf.get(reverse('myapp:login'))
response = MyLoginView.as_view()(request)
assert response.status_code == 200
Это не удается, потому что это представление вызывает базу данных, чтобы получить текущий сайт с помощью функции get_current_site()
:
Failed: Database access not allowed
Как я могу имитировать get_current_site()
, чтобы избежать попадания в базу данных?
Идея состоит в том, чтобы использовать фабрику с pytest-factoryboy.
Мне удалось издеваться над LoginView.get_context_data
, но я не могу углубиться:
from django.test import RequestFactory
from .views import MyLoginView
from django.contrib.sites.models import Site
from pytest_factoryboy import register
from unittest.mock import patch
rf = RequestFactory()
class SiteFactory(factory.Factory):
class Meta:
model = Site
register(SiteFactory)
def test_login_social(rf, site_factory):
request = rf.get(reverse('myapp:login'))
with patch(
# 'django.contrib.auth.views.LoginView.get_context_data', # This is wrong
'django.contrib.auth.views.get_current_site', # Solution: Patch where it is imported, this works!
return_value=site_factory(name='example.com'),
):
response = CommunityLoginView.as_view()(request)
assert response.status_code == 200
Решение состоит в том, чтобы исправить вызываемый метод в области, где он импортированный:
with patch('django.contrib.auth.views.get_current_site')
Здесь возникает ошибка из-за того, что context_data
является <class 'django.contrib.sites.models.Site'>
.
Как бы ты это сделал?
У вас есть два варианта:
pytest
допускает только доступ к базе данных, если вы явно отметите тестовую функцию, которая будет попадать в базу данных. Без этой информации pytest
запустит тест без создания базы данных для тестов. Рекомендую использовать pytest-django
и предоставленный декоратор pytest.mark.django_db
.
Вы добавили Сайт-Фреймворк в свой INSTALLED_APPS
. Это приложение необязательно, но полезно, если вы обслуживаете несколько разных страниц из одного приложения Django. Было время, когда Site-Framework был обязательным, но, поскольку он не обязателен, я редко включаю его в свой INSTALLED_APPS
. Может, тебе стоит оставить это.
Обновлено: Издевательство
Конечно, насмешка тоже должна работать, так как каждый объект в python является макетом (даже небольшие числа). Имейте в виду, что вам нужно исправить модуль / функцию импортируется, потому что он привязан к локальной области.
Чтобы найти нужное место, вы можете либо выполнить поиск в Исходный код Django, посмотреть, как он используется и как правильно его исправить, либо попытаться зайти в PDB
. Я не уверен, какой способ сработает, но я предлагаю вам 2 варианта:
pytest --pdb
python -m pdb pytest
. Это мгновенно откроет отладчик, и вам нужно будет один раз запустить continue
. pytest
теперь будет работать, пока не произойдет первое исключение, и PDB
запустится автоматически.Теперь вы можете использовать bt
(обратная трассировка), u
(обходить стек вверх), l
(показывать исходный код) и d
(обходить стек вниз), чтобы найти место доступа к базе данных.
РЕДАКТИРОВАТЬ2: заводчик
Если вы используете factoryboy
, от строить стратегию зависит, пытается ли он получить доступ к базе данных или нет. Стратегия по умолчанию - .create()
, которая выполняет запись в базу данных.
Он должен работать, если вы используете site_factory.build()
, поскольку он не получит доступ к вашей базе данных.
@raratiru Я обновил свой ответ, чтобы помочь найти правильный способ имитировать данную функцию. Может быть, кроме get_current_site()
есть еще несколько обращений к базе данных.
Спасибо за трюк с pdb и ссылки. Мне удалось издеваться над LoginView.get_context_data
, но я не могу углубиться и найти ни current_site
или get_current_site
ааа ... теперь я вижу проблему! Как-то упустил из виду, что вы пытаетесь создать объект Site
с помощью factoryboy
. Если вы создаете экземпляр с помощью factoryboy
, от строить стратегию зависит, пытается ли он получить доступ к базе данных или нет. Стратегия по умолчанию - .create()
, которая выполняет запись в базу данных. Он должен работать, если вы используете site_factory.build()
, поскольку он не имеет доступа к вашей базе данных.
Спасибо, в базу данных не попадает, потому что мне удалось пропатчить метод get_context_data
без попадания в базу данных. На самом деле я не могу исправить переменную, определенную внутри этого метода. class()
-> def method(self)
-> variable = call_to_another_module()
.
Но разве вы не используете site_factory(name='example.com')
в своем тесте? В таком случае site_factory(name='example.com')
попадает в базу данных, а site_factory.build(name='example.com')
- нет.
Я попробовал. В любом случае сообщение об ошибке - AttributeError: <function LoginView.get_context_data at 0x7f345156b048> does not have the attribute 'current_site'
. Я подозреваю, что нам нужен экземпляр LoginView, а не класс, и метод get_context_data
должен вызываться с request
в качестве аргумента. Как это может быть сделано? Предположим, что django.contrib.auth.views.LoginView.return_value
приводит пример, как насчет аргумента request
?
ОК ... как ты сказал! Мне пришлось пропатчить его в модуле, куда он был импортирован: django.contrib.auth.views.get_current_site
, а не через LoginView
. Спасибо!
Спасибо! На самом деле я не собираюсь тестировать Django. Это просто хитрый пример, чтобы понять, «как» и как далеко
patch
может это сделать.