I am doing my first experiments with django testing and I am having the problem that I always get the 404 template regardless which url (even /) I am using.
If I throw the very same code into the django shell it's working as expected and always presents me the contents of the requested url.
class SimpleTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', 'test', 'test')
self.user.is_staff = True
self.user.save()
self.client = Client()
def test_something(self):
self.assertTrue(self.client.login(username='test', password= 'test'))
self.client.get("/")
The login returns True, but the get() fails. Any hints what I am doing wrong here?
Keep in mind that most views use something like get_object_or_404, get_list_or_404, or simply raise Http404 when there's a problem accessing some object or another. You'll need to make sure that your test database is populated with sufficient objects to fulfill all these requirements to make the view not return a 404.
Remember, when running tests, the database is rolled back after each test (using transactions), so each test method must stand on its own or the setUp method must populate the database with any required dependencies.
Related
I am trying to make a fixture that returns an APIClient object that is authenticated for a user that I can pass a parameter to if I need. I have a DjangoModelFactory object called CustomerFactory that can create a customer and a user, the user of which is created with a UserFactory factory. I want to be able to access the data in the customer that is created, but also have a fixture to make an authenticated API request. This api_customer_client is what I came up with, and it doesn't work.
#pytest.fixture
def api_client():
return APIClient()
#pytest.fixture
def api_customer_client(app_customer, api_client):
def _api_customer_client(test_customer=app_customer):
refresh = RefreshToken.for_user(test_customer)
api_client.credentials(HTTP_AUTHORIZATION=f"JWT {refresh.access_token}")
return api_client
return _api_customer_client
I am calling the fixture with this test:
def test_client_cant_view_users_without_token(self, api_customer_client, app_customer):
client = api_customer_client(test_customer=app_customer.user)
result = client(reverse("api:user-list"), format="json")
assert result.status_code == 401
I keep getting the error TypeError: 'APIClient' object is not callable, and I can't figure out why. I originally thought it might be having trouble going through the api_customer_client fixture and returning a different fixture, but I have tried to just use APIClient directly in the api_customer_client fixture, and that didn't work either.
I have another fixture that is nearly identical, except for the sub-method thing, and it works perfectly:
#pytest.fixture
def api_user_client(user: User, api_client):
refresh = RefreshToken.for_user(user)
api_client.credentials(HTTP_AUTHORIZATION=f"JWT {refresh.access_token}")
return api_client
I hope I didn't go on too long with the explanation, but is this possible to do?
I made a dumb mistake. I was looking at the fixture as the problem and not the test.
I completely rewrote my test, but I think the issue was the line:
result = client(reverse("api:user-list"), format="json")
should have said:
result = client.post(reverse("api:user-list"), format="json")
I believe this code will still work, even though the code I ended up using was somewhat different.
I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed
The decorator is working fine but I would like to display an error message (I'd like to use messages framework) if the user doesn't belong to any of the required groups. Here's the decorator:
def group_required(*group_names):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(user):
if user.is_authenticated():
if bool(user.groups.filter(name__in=group_names)) or user.is_superuser:
return True
return False
return user_passes_test(in_groups)
I call it using something like:
#require_http_methods(['GET'])
#group_required('supervisor')
def home_view(request):
return render(request, 'home.html')
I tried using this snippet to use messages framework (since this requires the request object) but it realized that messages framework middleware didn't appear installed inside the decorator.
I'm willing to change whatever it takes :)
Update:
What I'm looking for:
def group_required(request, *group_names):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(user):
if user.is_authenticated():
if user.groups.filter(name__in=group_names).exists() or user.is_superuser:
return True
else:
# I'm getting:
# You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
messages.add_message(request, messages.ERROR, 'Group is not allowed')
return False
return user_passes_test(in_groups, request)
I don't think you really need threadlocals in this use case. And normally when threadlocals seems to be the only way to go in a Django app, there could be some mis-structured context layers. Comparing w/ the threadlocals, I would rather to duplicate user_passes_test and then modify it to pass request to in_groups (We could not pass request to is_group easily without modifying the code of user_passes_test. Check the question: How to pass Django request object in user_passes_test decorator callable function.) (Maybe a ticket for this?)
Furthermore, bool(user.groups.filter(name__in=group_names)) would caused items to be retrieved to DB adapter and Python instance before deciding the existence, using exists() and thus user.groups.filter(name__in=group_names).exists() to directly return bool result from DB backend is far more efficient here.
I'm trying to figure out how to test middleware in django. The middleware I'm writing logs in a user under certain conditions (if a key sent in email is valid). So obviously I'm dependent on django.contrib.auth and django.contrib.sessions.
I'm running into problems testing the login portion. I'm making a request like this:
user = User.objects.create_user('user', 'user#example.org', 'password')
key = LoginKey.objects.create(user=user)
request = self.factory.get('/', data={'auth_key': key.hash}) # self.factory is a RequestFactory()
self.middleware.process_request(request) # self.middleware is MyMiddleware()
That fails due to the session not being set. So next, I wrote a little snippet in my test class:
def make_session(self, request):
SessionMiddleware().process_request(request)
and that fails due to 'User' object has no attribute 'backend'. I'm not sure on the meaning of that, but I suspect I need to run all the middlewares I have installed.
I don't really want to make a fake view for this just to run a middleware, but I can't see another option at this point.
So I just wanted to know, before I have to chase this rabbit all the way down the hole, is there a way of doing this that doesn't require as much duct tape?
You should use the test client for this. That will ensure that the middleware is run and the session keys created.
response = self.client.get('/?auth_key=%s' % key.hash)
self.assertTrue(response.context['user'].is_authenticated()) # for example
I'm trying to build a test for a view that's decorated with
#login_required, since I failed to make it work, I did a simple test
and still can't make it pass.
Here is the code for the simple test and the view:
def test_login(self):
user = self._create_new_user()
self.assertTrue(user.is_active)
login = self.client.login(username=user.username,
password=self.data['password1'])
self.failUnless(login, 'Could not log in')
response = self.client.get('/accounts/testlogin/')
self.assertEqual(response.status_code, 200)
#login_required
def testlogin(request):
print 'testlogin !! '
return HttpResponse('OK')
_create_new_user() is saving the user and there is a test inside that
method to see that is working.
The test fails in the response.status_code, returning 302 and the
response instance is of a HttpResponseRedirect, is redirecting it as
if not logged in.
Any clue? I'm missing something?
Regards
Esteban
This testcase works for me:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test.client import Client
import unittest
class LoginTestCase(unittest.TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
def testLogin(self):
self.client.login(username='john', password='johnpassword')
response = self.client.get(reverse('testlogin-view'))
self.assertEqual(response.status_code, 200)
I suggest you (if you don't use them already) to use the reverse() function and name your URLs. This way you are sure that you get always the right URL.
Here is the answer:
Python 2.6.5 made a change to the way
cookies are stored which is subtly
incompatible with the test client.
This problem has been fixed in the
1.1.X and trunk branches, but the fix hasn't yet made it into a formal
release.
If you are using 1.1.X and Python
2.6.5, you're going to have problems with any test activity involving
cookies. You either need to downgrade
Python, or use the 1.1.X branch rather
than the 1.1.1 release.
A 1.1.2 release (that will include the
fix for the problem you describe) will
be made at the same time that we
release 1.2 - hopefully, very very
soon.
Yours, Russ Magee %-)
http://groups.google.com/group/django-users/browse_frm/thread/617457f5d62366ae/05f0c01fff0b9e6d?hl=en&lnk=gst&q=2.6.5#05f0c01fff0b9e6d
OK I was to facing same problem #resto solved my problem.
creating user this way below, lets the test client get the user logged in and get the response other than redirect (302)
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')