Reputation: 9636
I am trying to understand how patching works and I am testing with 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
This fails because this View calls the database in order to get the current Site using the function get_current_site()
:
Failed: Database access not allowed
How can I mock get_current_site()
to avoid the database hit?
An idea is to use a factory with pytest-factoryboy.
I managed to mock LoginView.get_context_data
but I cannot go deeper:
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
The solution is to patch the method beeing called, at the scope where it is imported:
with patch('django.contrib.auth.views.get_current_site')
Here an error occurs due to the context_data
being a <class 'django.contrib.sites.models.Site'>
How would you do it?
Upvotes: 0
Views: 1503
Reputation: 744
You have two options here:
pytest
only allows a database access, if you explicitly mark the test function that we will hit the database. Without that information, pytest
will run the test without having constructed a database for tests. I recommend to use pytest-django
and the provided decorator pytest.mark.django_db
.
You have added the Site-Framework to your INSTALLED_APPS
. This app is optional, but useful if you serve multiple different pages from a single Django application. There was a time when the Site-Framework was mandatory, but since it is optional I rarely include in my INSTALLED_APPS
. Maybe you should leave it out to.
EDIT: Mocking
Sure, mocking should work as well, since every object in python is mockable (even small numbers). Keep in mind that you have to patch where the module/function is imported because it's bound to the local scope.
To find the right location, you could either search the Django source code, see how it is used and how to patch it correctly or try to drop into PDB
. I am not sure which way will work, but I provide you with 2 options:
pytest --pdb
python -m pdb pytest
. This will instantly open the debugger and you have to continue
once. pytest
will now run until the first exception occurs and PDB
will start automatically.You can now use bt
(backtrace), u
(walk stack up), l
(show source code) and d
(walk stack down) to find the location of the database access.
EDIT2: factoryboy
If you are using factoryboy
, it depends on the build strategy whether it tries to access the database or not. The default strategy is .create()
, which writes to the database.
It should work if you use site_factory.build()
, since this will not access your database.
Upvotes: 1