I have error like in title when I'm trying to run test, I dont know whats going on but my testUser doesn't work properly, It's funny because i have identical test user in another project and there everything is ok.
test_api.py
class TaskDetailViewAPI(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username='test', password='test123')
self.user.save()
#classmethod
def setUpTestData(cls):
user = User.objects.get(id=1)
Task.objects.create(name='TestTask', user=user, status='NEW', date=date(2019, 4, 9), description='This is test')
def test_access_to_view_logged(self):
task= Task.objects.get(id=1)
login = self.client.login(username='test', password='test123')
self.assertTrue(login)
And this is test from another project where everything works fine
class CreateCommentAPI(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username='test', password='test123')
self.user.save()
#classmethod
def setUpTestData(cls):
Category.objects.create(name='PC', slug='pc')
Product.objects.create(
category=Category.objects.get(id=1),
name='Laptop', slug='laptop',
description='here is description',
photo=SimpleUploadedFile("file.jpeg", b"file_content", content_type="image/jpeg"),
price=1999, available='available'
)
def test_access_to_view_logged(self):
product = Product.objects.get(id=1)
login = self.client.login(username='test', password='test123')
response = self.client.get(reverse('add_comments', kwargs={'id': product.id}))
self.assertTrue(login)
self.assertEqual(response.status_code, 200, f'expected Response code 200, instead get {response.status_code}')
setUpTestData is called only once for the whole test class, but more importantly it is called before setUp.
Your working code doesn't have anything inside setUpTestData that depends on data in setUp, which is correct. But your non-working code does; it tries to access the User, which hasn't been created yet. You need to refactor things so that the User is either created inside setUpTestData, or the Task is created inside setUp.
Your test user's id might not be 1, instead of using the id, you could use the username in your setUpTestData method:
user = User.objects.get(username='test')
TL;DR: In my case, I forgot to create and apply the migrations:
python manage.py makemigrations
python manage.py migrate
Longer, I have modified Django models, but did not push/migrate the changes into the database, which actually holds the data about those models. So when my code asked for the model data, they were not in the DB, thus the matching query does not exist.
This is not the case for the OPs question, although my answer could be a solution to the error mentioned in the title, which got me here in the first place.
Related
Python 3.10.7, Django 4.1.1, Django REST Framework 3.13.1
I am unable to get the django.test.Client login or force_login methods to work in a django.test.TestCase-derived test class. I'm referring to https://docs.djangoproject.com/en/4.1/topics/testing/tools/
My project seems to work when viewing it in a browser. Unauthenticated DRF views appear as expected, and if I log in through the admin site, protected views also appear as expected. A preliminary version of the front end that will consume this API is able to read data and display it with no problem. The local Django unit test environment uses a local SQLite3 install for data. All tests not requiring authentication are currently passing.
This simplified test class reliably displays the problem:
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from eventsadmin.models import Address
class AddressesViewTest(TestCase):
username = "jrandomuser"
password = "qwerty123"
user = User.objects.filter(username=username).first()
if user:
print("User exists")
else:
user = User.objects.create(username=username)
print("User created")
user.set_password(password)
user.save()
client = Client()
def setUp(self):
if self.client.login(username=self.username, password=self.password):
print("Login successful")
else:
print("Login failed")
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188")
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
First, I was surprised that I had to test for the existence of the User. Even though the test framework emits messages saying it is creating and destroying the test database for each run, after the test has been run once the creation of the User fails with a unique constraint violation on the username. If I don't change the value of username the test as written here consistently emits User exists. This is the only test currently creating/getting a User so I'm sure it's not being created by another test.
The real problem is setUp. It consistently emits Login failed, and test_addresses fails on access permissions (which is correct behavior when access is attempted on that view without authentication). If I set a breakpoint in the last line of setUp, at that point self.client is an instance of django.test.Client, and self.username and self.password have the expected values as set above.
I tried replacing the call to login with self.client.force_login(self.user) but in that case when that line is reached Django raises django.db.utils.DatabaseError: Save with update_fields did not affect any rows. (the stack trace originates at venv/lib/python3.10/site-packages/django/db/models/base.py", line 1001, in _save_table).
What am I doing wrong? How can I authenticate in this context so I can test views that require authentication?
I've been doing a bunch of tests recently and here's how are all of mine create the user inside of the setup, like the block below, give that a shot.
class AddressesViewTest(TestCase):
def setUp(self):
self.username = "jrandomuser"
self.password = "qwerty123"
user = User.objects.create(username=self.username)
user.set_password(self.password)
user.save()
self.client.login(username=self.username, password=self.password)
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188"
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
And actually I don't even login during the setUp because I want to make sure the view has a #login_required, so it do it in the test itself:
class AddressesViewTest(TestCase):
def setUp(self):
self.username = "jrandomuser"
self.password = "qwerty123"
user = User.objects.create(username=self.username)
user.set_password(self.password)
user.save()
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188"
def test_anonymous(self):
response = testObj.client.get(reverse("addresses-list"))
testObj.assertEqual(response.status_code, 302, 'address-list #login_required Missing')
def test_addresses(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
From what I've noticed is that setUp is ran per test. So in my last example the user would be created for anonymous, deleted or reverted, created for test_addresses. So having the user outside of that block is probably leading to the user not being deleted/reverted which is leading to some funky behavior.
And I know the tests say it removed the db every single time, without the --keepdb flag, but I'm starting to doubt that.. cause I've been hitting some weird behavior and it's only after I run the test back-to-back-to-back-to-back.. something is off forsure
A friend with more experience put me onto what seems to be the right track, which is a setUpTestData class method, that gets called only once. I would not have thought of this myself because I imagined classmethod to be similar to static in .NET or Java, but apparently not; I have quite a bit more to learn here.
Mostly all test data creation not specific to a particular test ought to go in setUpTestData, also, he says.
This works:
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from eventsadmin.models import Address
class AddressesViewTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create(username="jrandomuser")
cls.client = Client()
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188")
def setUp(self):
self.client.force_login(self.user)
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
As #nealium points out, it also makes sense to move the call to login or force_login into the test if there are any tests where you don't want the Client to be authenticated.
im trying to run the following test but im not sure why its not working since when tests are ran a test db is created and that means that the newly created item gets an id of 1 but im still getting an error that no object matches the query
model
from django.db import models
# Create your models here.
class Post(models.Model):
text = models.TextField()
def __str__(self):
return self.text[:50]
tests
from django.test import TestCase
from .models import Post
# Create your tests here.
class PostModelTest(TestCase):
def setup(self):
post = Post.objects.create(text="just a test")
def test_text_content(self):
post = Post.objects.get(id=1)
expected_obect_name = f'{post.text}'
self.assertEqual(expected_obect_name, 'just a test')
here's the error
First of all you should name method setUp not setup. It's case-sensetive. But it's probably will not solve your problem, since django doesn't reset auto id for each TestCase class. So if you have another test case with same code the error will happened again. To solve it you need to save post id as e.g. self.post_id and use it in test methods instead of 1:
class PostModelTest(TestCase):
def setUp(self):
post = Post.objects.create(text="just a test")
self.post_id = post.pk
def test_text_content(self):
post = Post.objects.get(id=self.post_id)
expected_obect_name = f'{post.text}'
self.assertEqual(expected_obect_name, 'just a test')
I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.
I have written my test cases in two separate test files (e.g. test_1 and test_2). In both of test cases that I am testing my models I have code duplications because of similar processes.
For example, I need to login the user and test the credential.
Sample of code:
import test_data
from django.test import TestCase
from UserData.models import MyModel
from django.contrib.auth.models import User
class UserDataMyModelTestCalls(TestCase):
#classmethod
def setUpTestData(cls):
cls.test_user = User.objects.create_user(test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'])
def test_faulty_login_credentials(self):
self.client.login(username=test_data.faulty_user_data['user_name'], password=test_data.faulty_user_data['password'])
response = self.client.get('/userdata/mymodelurl/', {})
self.assertEqual(response.status_code, 403)
I am using a separate file with user credentials to avoid duplications again.
Sample of test_data file:
test_user_data = {'id': u'1',
'user_name': 'tempUsername',
'password': 'tempPassword',
'email': 'tempEmaily#test.com'}
Update: Adding the UserTests class that I want to use as a common class for all my test cases. I am defining and calling the test through the test_1.py like this:
import UserTests
from django.test import TestCase
class UserDataWayPointTestCalls(TestCase):
testCasesObject = UserTests.UserDataTestCalls()
test_user = testCasesObject.setUpTestData()
response = testCasesObject.test_faulty_login_credentials()
My UserDataTestCalls class is defined like this:
import test_data
from django.test import Client
from django.test import TestCase
from django.contrib.auth.models import User
class UserDataTestCalls(TestCase):
def __init__(self):
self.test_user = None
self.faulty_login_response = None
def setUpTestData(self):
self.client = User.objects.create_user(test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'])
self.client = Client()
return self.client
def test_faulty_login_credentials(self):
self.client.login(username=test_data.faulty_user_data['user_name'],
password=test_data.faulty_user_data['password'])
response = self.client.get('/userdata/mymodelurl/', {})
return response
When I execute the code above I get IntegrityError: (1062, "Duplicate entry 'tempUsername' for key 'username'"). Temporarily I modify the username value to proceed and I get the following error AttributeError: 'UserDataTestCalls' object has no attribute '_testMethodName'.
I tried to create a separate class with name e.g. UserDataTestCalls and include the common parts of my test cases such as User.objects.create_user, self.client.login etc...
Unfortunately I end up getting errors that the database although it said Destroying test database for alias 'default'... on the next run I got username duplications e.g. Duplicate entry 'tempUsername' for key 'username' etc...
When I tried to overcome this problem by changing the username for testing purposes then I got another problem 'NoneType' object has no attribute 'login'.
Which it points that the self.client variable is not binded with the test_user that I am creating.
I tried to search online and find documentation on how to overcome my problem but all the documentation are pointing to use separate scripts for your tests individually, which I can understand if you have different test cases. In my case 90% of my test cases are exactly the same.
So I am sure there is a way to create a user in a separate class and create all my test cases in that class too, so I could call them from a separate test file(s) when I need them.
Can someone point me to the correct direction or provide some links with examples/documentation that I could read from?
Thank you in advance for your time and effort.
Try creating a common test class.
class CreateUserTestCase(TestCase):
def setUpTestData(self):
self.user = User.objects.create_user(
test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'],
)
You want to assign the new user to self.user. Don't replace self.client which should be the test client, not the user. You don't need to do self.client = Client(), the Django test case will take care of this for you.
Then subclass the test case and add your tests.
class UserDataTestCalls(CreateUserTestCase):
def test_faulty_login_credentials(self):
self.client.login(
username=test_data.faulty_user_data['user_name'],
password=test_data.faulty_user_data['password'],
)
response = self.client.get('/userdata/mymodelurl/', {})
return response
From your question, I wasn't sure if test_data is different for each class. If so, you'll have to change this slightly.
I have the application in Django REST as backend and Angular as frontend.
Suppose in This is my code
class ModelClass (models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def save(self, *args, **kwargs):
#check if the row with this hash already exists.
if not self.pk:
self.hash = self.create_hash()
self.my_stuff = 'something I want to save in that field'
# call to some async task
super(ModelClass, self).save(*args, **kwargs)
In my REST i have this view
class ModelListCreateView(generics.ListCreateAPIView):
model = ModelClass
serializer_class = ModelClassSerializer
def pre_save(self, obj):
obj.created_by = obj.updated_by = self.request.user.staff
def post_save(self, obj, created=False):
# add some other child objects of other model
I don't want to do unit testing. I want to do system testing so that I need to know if I post something to that view then
Pre-save thing should work
Record gets created
Save method of Model gets called with his stuff
After save method in REST gets called
Then I can assert all that stuff.
Can I test all that . I want to know which thing I need to have that sort of test rather than small unit tests
I am confused do I need to use Django test or REST Test or selenium test or PyTEst or factory boy because i want to know if things are actually getting in database
What you are looking is some kind of a REST Client code that would then be able to run your tests and you would be able to verify if the call is successful or not. Django Rest Framework has the APIRestFactory helper class that will aid you in writing such tests. The documentation can be found here and specifically look at the Example section. This would be part of your Django tests