I new to Django and I'm trying to implement unittest. Is there a way to test the data saved in a session ?
Here is a simple view, where dummy data is saved in the session.
def save_data_in_session(request):
request.session["hello"] = "world"
# If I print(request.session.items()), output is dict_items([('hello', 'world')]) as expected.
context = {}
return render(request, "home.html", context)
Here is my unittest
class TestDummyViews(TestCase):
def test_save_data_in_session(self):
session = self.client.session
url = reverse("home_page:save_data_in_session")
response = self.client.get(url)
session.save()
# If I print(session.items()) output is dict_items([])...
# Following test passes
self.assertEqual(response.status_code, 200)
# Following test fails. KeyError: 'hello'
self.assertEqual(
session["hello"],
"world",
)
Related
I have a Django function-based form view that initializes a form with default data on a GET request, and saves the model object on a POST request:
def copy(request, ann_id):
new_ann = get_object_or_404(Announcement, pk=ann_id)
new_ann.pk = None # autogen a new primary key (quest_id by default)
new_ann.title = "Copy of " + new_ann.title
new_ann.draft = True
new_ann.datetime_released = new_ann.datetime_released + timedelta(days=7)
form = AnnouncementForm(request.POST or None, instance=new_ann)
if form.is_valid():
new_announcement = form.save(commit=False)
new_announcement.author = request.user
new_announcement.datetime_created = timezone.now()
new_announcement.save()
form.save()
return redirect(new_announcement)
context = {
"title": "",
"heading": "Copy an Announcement",
"form": form,
"submit_btn_value": "Create",
}
return render(request, "announcements/form.html", context)
I can't figure out how to test the form.is_valid() branch when the form is posted, without manually providing the form data to self.client.post(url, form_data) in my view.
Here' what I'm trying:
test_views.py
class AnnouncementViewTests(TestCase):
def setUp(self):
self.client = ...
... etc
def test_copy_announcement(self):
# log in a teacher
success = self.client.login(username=self.test_teacher.username, password=self.test_password)
self.assertTrue(success)
# hit the view as a get request first, to load a copy of the announcement in the form
response = self.client.get(
reverse('announcements:copy', args=[self.test_announcement.id]),
)
self.assertEqual(response.status_code, 200)
# The form in this response should be valid, and should bw
# accepted on a post request,
# that is what I am testing in this integration test.
form_data = response.how_do_get_the_form_data() # ???????
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data
)
# Get the newest announcement just made in the post request
new_ann = Announcement.objects.latest('datetime_created')
self.assertRedirects(
response,
new_ann.get_absolute_url()
)
What I want to actually test is that the result of the get provides valid default data for the form that can then be submitted via post request.
But I can't figure out how to access the form data resulting from the get request, so I can then feed it into the form_data provided to the post request.
EDIT
I found the location of the form in the get response, but I have no idea how to get that in code.
You can access the response form in this way:
response.context['form']
From here you can build your payload in this way:
retrieved_instance = response.context['form'].instance
form_data = dict(title=retrieved_instance.title, ... <all the other fields>)
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data)
)
This is not like resubmitting the page but is very similar because you're resubmitting the same form.
Actually, your test seems more like an e2e test (when speaking about integrations and e2e there is some ambiguity let's say), for this reason, if I were you, I would switch "tool" and use selenium to simulate a user interaction from the beginning (open an existing announcement) to the end, pressing the submit button on your web page.
Only in this way what you're submitting is the "real" response of the "get"
If you are new to this kind of test, you can find here a simple tutorial https://lincolnloop.com/blog/introduction-django-selenium-testing/ to understand the main concepts.
Your question is bit confusing but I will try to head you in right direction.
To clean-up the code you should use Class Based View where you can use your form easily anywhere. A sample code I just wrote:
class TestView(View):
template_name = 'index.html'
success_url = 'home' # dummy view
context = {"form": myform()}
# myform is the definition/class of your form which contains all attrs.
def get(self, request):
context = self.context
context['form'] = form # fill out your data here for get request
return render(request, self.template_name, context)
def post(self, request):
context=self.context
# self.context certain that you're using exact form which you defined in class-scope
form=context['form']
# Form Validation
if form.is_valid():
#perform any logical validations here and save the form if required
return redirect(self.success_url)
context = self.context
context['form'] = form # just to show you that you can access that form anywhere
return render(request, self.template_name, context)
You coul manually pass data to you form like this and test the is_valid function in a unit test:
form_data = {'my': 'value', 'form': 'value', 'fields': 'value'}
form = AnnouncementForm(form_data)
self.assertFalse(form.is_valid)
I'm trying to test an django ajax view that saves a field. Here's the test:
def test_ajax_save_draft(self):
self.client.force_login(self.test_user) # view requires login
sub = mommy.make(QuestSubmission, quest=self.quest2)
draft_comment = "Test draft comment"
# Send some draft data via the ajax view, which should save it.
ajax_data = {
'comment': draft_comment,
'submission_id': sub.id,
}
self.client.post(
reverse('quests:ajax_save_draft'),
data=ajax_data,
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
)
self.assertEqual(draft_comment, sub.draft_text) # sub.draft_text = None, as if the ajax view was never called!
And here's the view:
#login_required
def ajax_save_draft(request):
if request.is_ajax() and request.POST:
submission_comment = request.POST.get('comment')
submission_id = request.POST.get('submission_id')
sub = get_object_or_404(QuestSubmission, pk=submission_id)
sub.draft_text = submission_comment
sub.save()
response_data = {}
response_data['result'] = 'Draft saved'
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
else:
raise Http404
When I run the test, I get into the if block, and it can retrieve the comment and submission object, but when it returns to the test at the end, it's like the save never happened.
What am I doing wrong here?
Try calling sub.refresh_from_db()
Changes made to a model instance will only affect the instance being modified. Any existing model instances will not be updated, refresh_from_db will get the changes from the database
I receive this message "feed.models.Post.DoesNotExist: Post matching query does not exist." I believe it to be in the UpdatePost class I dont understand as there is a post created with an id of one. Why is this? Edit : I've added delete to fully test CRUD functionality
from django.test import TestCase, SimpleTestCase
from django.contrib.auth.models import User
from django.urls import reverse
from feed.models import Post
class Setup_Class(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='jtur', email='jtur#accenture.com', password='onion')
user = User.objects.first()
Post.objects.create(title='test', content='more testing', author=user)
class PostTests(Setup_Class):
def test_content(self):
post = Post.objects.get(id=1)
expected_post_title = f'{post.title}'
expected_post_content = f'{post.content}'
self.assertEquals(expected_post_title, 'test')
self.assertEquals(expected_post_content, 'more testing')
def test_post_list_view(self):
response = self.client.get(reverse('feed-home'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'more testing')
self.assertTemplateUsed(response, 'feed/home.html')
class UpdatePost(Setup_Class):
def test_post_update(self):
post = Post.objects.first()
post.title = "This has been changed"
expected_post_title = f'{post.title}'
self.assertEquals(expected_post_title, 'This has been changed')
def test_post_updated_view(self):
response = self.client.get(reverse('feed-home'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'This has been changed')
self.assertTemplateUsed(response, 'feed/home.html')
class DeletePost(Setup_Class):
def test_post_delete(self):
post = Post.objects.first()
post.delete()
val = False
if post is None:
val = True
else:
val = False
self.assertTrue(val)
def test_post_list_view(self):
response = self.client.get(reverse('feed-home'))
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'test')
self.assertTemplateUsed(response, 'feed/home.html')
There's no reason to assume the post created in your setUp method will have id=1. In fact, it probably won't after the first run of your tests. Even though the database is emptied after each run, the sequences are usually not reset.
You should get the first post with Post.objects.first() instead.
(Note however that your test_content and test_post_update methods are pretty pointless; they only call native Django functionality, which you don't need to test. Your tests should be concerned with testing your app's functionality, such as views that update or display the posts.)
I'm trying to write tests for an Admin action in the change_list view. I referred to this question but couldn't get the test to work. Here's my code and issue:
class StatusChangeTestCase(TestCase):
"""
Test case for batch changing 'status' to 'Show' or 'Hide'
"""
def setUp(self):
self.categories = factories.CategoryFactory.create_batch(5)
def test_status_hide(self):
"""
Test changing all Category instances to 'Hide'
"""
# Set Queryset to be hidden
to_be_hidden = models.Category.objects.values_list('pk', flat=True)
# Set POST data to be passed to changelist url
data = {
'action': 'change_to_hide',
'_selected_action': to_be_hidden
}
# Set change_url
change_url = self.reverse('admin:product_category_changelist')
# POST data to change_url
response = self.post(change_url, data, follow=True)
self.assertEqual(
models.Category.objects.filter(status='show').count(), 0
)
def tearDown(self):
models.Category.objects.all().delete()
I tried using print to see what the response was and this is what I got:
<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/admin/login/?next=/admin/product/category/">
It seems like it needs my login credentials - I tried to create a user in setUp() and log in as per Django docs on testing but it didn't seem to work.
Any help would be appreciated!
I found the solution - I wasn't instantiating Django's Client() class when I created a superuser, so whenever I logged in - it didn't persist in my subsequent requests. The correct code should look like this.
def test_status_hide(self):
"""
Test changing all Category instances to 'Hide'
"""
# Create user
user = User.objects.create_superuser(
username='new_user', email='test#example.com', password='password',
)
# Log in
self.client = Client()
self.client.login(username='new_user', password='password')
# Set Queryset to be hidden
to_be_hidden = models.Category.objects.values_list('pk', flat=True)
# Set POST data to be passed to changelist url
data = {
'action': 'change_to_hide',
'_selected_action': to_be_hidden
}
# Set change_url
change_url = self.reverse('admin:product_category_changelist')
# POST data to change_url
response = self.client.post(change_url, data, follow=True)
self.assertEqual(
models.Category.objects.filter(status='show').count(), 0
)
I have a weird error writing a APITestCase for a Django REST view.
This is my code:
class CreateUserTest(APITestCase):
def setup(self):
self.superuser = User.objects.create_superuser('vishnu#vishnu.com', '1989-10-06', 'vishnupassword')
self.client.login(username='vishnu', password='vishnupassword')
self.data = a bunch of trivial data
def test_can_create_user(self):
print "create user"
self.setup()
self.token = Token.objects.get(user_id=self.superuser.id)
self.api_key = settings.API_KEY
self.factory = APIRequestFactory()
self.request = self.factory.post('/api/v1/uaaaaaasers/?api_key=%s' % self.api_key,
self.data,
HTTP_AUTHORIZATION='Token {}'.format(self.token))
force_authenticate(self.request, user=self.superuser)
self.view = UserList.as_view()
self.response = self.view(self.request)
self.response.render()
#print self.response.content
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
As you see I run a factory.post to an intentionally wrong url /api/v1/uaaaaaasers/
But the test doesnt complain:
Creating test database for alias 'default'...
create user .
----------------------------------------------------------------------
Ran 1 test in 0.199s
OK Destroying test database for alias 'default'...
Shouldnt it crash with that wrong url? How do I know the test is going fine?
You are testing it all wrong...
The response that you have tested is from the direct view call...
self.view = UserList.as_view()
self.response = self.view(self.request)
self.response.render()
#print self.response.content
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
your above case will always call the view...
In actual testcases we hit the urls with the client and test that response
self.response = self.client.post('/api/v1/uaaaaaasers/?api_key=%s' % self.api_key,
self.data,
HTTP_AUTHORIZATION='Token {}'.format(self.token))
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
If you want to test posting a request to an invalid url, use the test client instead of the request factory.
class CreateUserTest(APITestCase):
def test_can_create_user(self):
...
response = self.client.post(
'/api/v1/uaaaaaasers/?api_key=%s' % self.api_key,
self.data,
...
)
...