responseType blog in django unit tests - django

How to add responseType blob while making a post request with django_test unit tests, below is my test code snippet :
from django.test import TestCase
def test_export_business_plan(self):
"""Test export business plan."""
data = {
'location_creation': 'berlin',
'date_creation': datetime.now().strftime('%Y-%m-%d'),
'company_name': 'Captiq',
'email_address': 'test#captiq.com',
'phone_number': '0704594180',
'street': 'Berlin Katherinenstrasse',
'house_number': '17b',
'additional': 'test',
'post_code': '43567',
'city': 'Frankfurt',
'sub_title': 'test'
}
response = self.client.post(
reverse(
'api:financing:business_plan-export',
kwargs={'pk': self.loan_application.pk}), data)
print('**********==', response.data)
print('**********==', dir(response))
self.assertEqual(response.status_code, 200)

Related

Django rest framework custom renderer in testing response

I'm trying to make my testing code in my django app. My app has a custom renderer class so the response will look like this:
{
"code": 422,
"message": "Unprocessable Entity",
"data": {
"non_field_errors": [
"Unable to log in with provided credentials."
]
}
}
My test code:
class AuthenticationTestCase(TestCase):
"""Test login and registration"""
def setUp(self):
self.client = APIClient()
def test_login_success(self):
"""Test success login"""
payload = {
'email': 'test#test.com',
'password': 'password',
'is_active': True
}
create_user(**payload)
res = self.client.post(LOGIN_URL, payload)
self.assertEqual(res.data['code'], status.HTTP_200_OK)
The client response only return:
"non_field_errors": [
"Unable to log in with provided credentials."
]
Question is, can I make my testing client response using my custom renderer class as well?
EDIT:
This is my custom renderer class:
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
status_code = renderer_context['response'].status_code
response = {
'code': status_code,
'message': renderer_context['response'].status_text,
'data': data,
}
renderer_context['response'].status_code = 200
return super(CustomRenderer, self) \
.render(response, accepted_media_type, renderer_context)
I found the solution. I need to set response's data in renderer class to my custom response like this:
renderer_context['response'].data = response

Django - approach to code repetition in Unit tests

Short introduction.
I'm just learning to write tests for my application. The application is an online gradebook.
Below is the code from the homepage view test. Only the login form is there. If the user logs in for the first time, he is redirected to the view that forces to change the default password. If this is the next login, it is redirected to the right view, depending on the user rights.
Problem.
In this test of the view, I had to create 6 users and related models (Student, Parent, Teacher), so I wrote the methods to create these objects (create_user / create_person).
In many future view tests, I will also have to log in users and use models (Student, Parent, Teacher).
How should I repeat these steps? Two ways come to mind.
1. Convert methods to static and use them in the next test classes to create objects. Will the tests still be "unitary"?
2. Copy the method definitions to each next test class in which they will be needed. Will this not break the DRY principle?
Which approach is good?
class HomepageViewTestCase(TestCase):
password = 'testerspass'
def create_user(self):
username = 'tester'
numbering = 0
while True:
try:
User.objects.get(username=username)
numbering += 1
username += str(numbering)
except User.DoesNotExist:
return User.objects.create_user(
username=username, password=self.password,
first_name='Name', last_name='Surname')
def create_person(self, kind):
content_type = ContentType.objects.get_for_model(RightsSupport)
permission = Permission.objects.get(content_type=content_type,
codename=kind)
if kind in {'student', 'parent'}:
user = self.create_user()
user.user_permissions.add(permission)
try:
school_class = SchoolClass.objects.get(unique_code='2a2020')
except SchoolClass.DoesNotExist:
school_class = SchoolClass.objects.create(unique_code='2a2020',
name='2a', year=2020)
student = Student.objects.create(user=user,
school_class=school_class,
name=user.first_name,
surname=user.last_name,
birthday='2010-03-29')
if kind == 'student':
return student
if kind == 'parent':
user = self.create_user()
user.user_permissions.add(permission)
return Parent.objects.create(user=user, student=student,
name=user.first_name,
surname=user.last_name)
if kind == 'teacher':
user = self.create_user()
user.user_permissions.add(permission)
return Teacher.objects.create(user=user, name=user.first_name,
surname=user.last_name)
def setUp(self):
# Create users and student/parent/teacher models objects to test login
# by homepage
self.student_1 = self.create_person('student')
self.student_1_form_data = {'username': self.student_1.user.username,
'password': self.password}
self.student_2 = self.create_person('student')
self.student_2.first_login = False
self.student_2.save()
self.student_2_form_data = {'username': self.student_2.user.username,
'password': self.password}
self.parent_1 = self.create_person('parent')
self.parent_1_form_data = {'username': self.parent_1.user.username,
'password': self.password}
self.parent_2 = self.create_person('parent')
self.parent_2.first_login = False
self.parent_2.save()
self.parent_2_form_data = {'username': self.parent_2.user.username,
'password': self.password}
self.teacher_1 = self.create_person('teacher')
self.teacher_1_form_data = {'username': self.teacher_1.user.username,
'password': self.password}
self.teacher_2 = self.create_person('teacher')
self.teacher_2.first_login = False
self.teacher_2.save()
self.teacher_2_form_data = {'username': self.teacher_2.user.username,
'password': self.password}
def test_homepage_view(self):
response = Client().get(reverse('yourgrades:homepage'))
self.assertEqual(response.status_code, 200)
# Post with clean form or with wrong data -> not redirects
response = Client().post(reverse('yourgrades:homepage'))
self.assertEqual(response.status_code, 200)
response = Client().post(reverse('yourgrades:homepage'),
{'username': 'tester',
'password': 'wrongpass'})
self.assertEqual(response.status_code, 200)
# Student redirection tests
# if first_login field is True -> redirects to FirstLoginView
self.assertTrue(LoginForm(self.student_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.student_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.student_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to StudentParentView
self.assertTrue(LoginForm(self.student_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.student_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.student_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/studentparent')
self.assertTemplateUsed(response, 'yourgrades/studentparent.html')
# Parent redirection tests
# if first_login field is True, redirects to FirstLoginView
self.assertTrue(LoginForm(self.parent_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.parent_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.parent_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to StudentParentView
self.assertTrue(LoginForm(self.parent_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.parent_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.parent_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/studentparent')
self.assertTemplateUsed(response, 'yourgrades/studentparent.html')
# Teacher redirection tests
# if first_login field is True, redirects to FirstLoginView
self.assertTrue(LoginForm(self.teacher_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to TeacherPanelView
self.assertTrue(LoginForm(self.teacher_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/teacher')
self.assertTemplateUsed(response, 'yourgrades/teacher.html')```

TypeError: 'NoneType' object is not subscriptable in Django unittests for message

Am trying to do a unit test for a view that returns a message when some input is duplicated in the database.it gives me the error TypeError: 'NoneType' object is not subscriptable
Here is the view
#login_required
def worker_create(request):
worker_create_form = WorkerCreateForm(request.POST)
if request.method == 'POST':
if worker_create_form.is_valid():
form = worker_create_form.save(commit=False)
phone = form.phone
check_phone = Worker.objects.filter(phone=phone)
if check_phone.count() != 0:
messages.error(request, 'رقم الهاتف مستخدم من قبل')
else:
form.save()
return redirect('worker_list')
else:
worker_create_form = WorkerCreateForm(request.POST)
context = {
'worker_create_form': worker_create_form,
}
return render(request, 'erp_system/worker/create.html', context)
and here are the tests I've created for it
class WorkerCreateTest(TestCase):
def setUp(self):
User.objects.create_user(username='test_user', email='test#gmail.com', password='test_password')
branch = Branch.objects.create(name='test branch')
Worker.objects.create(name="ay btngan ", phone='01207199086', branch=branch)
def test_get_request_unauthenticated(self):
response = self.client.get(reverse('worker_create'))
url = reverse('worker_create')
self.assertRedirects(response, '/login/?next=' + url)
def test_get_request_authenticated(self):
self.client.login(username='test_user', password='test_password')
response = self.client.get(reverse('worker_create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed('erp_system/worker/create.html')
def test_post_request_empty_data(self):
self.client.login(username='test_user', password='test_password')
response = self.client.post(reverse('worker_create'), {})
self.assertFormError(response, 'worker_create_form', 'name', 'This field is required.')
self.assertFormError(response, 'worker_create_form', 'phone', 'This field is required.')
self.assertFormError(response, 'worker_create_form', 'branch', 'This field is required.')
def test_post_request_invalid_data(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
name = 'just a name'
for i in range(300):
name += 'h'
response = self.client.post(reverse('worker_create'),
{'name': name, 'phone': '01207199086', 'branch': branch.id})
self.assertEqual(response.status_code, 200)
def test_post_request_duplicated_phone(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
response = self.client.post(reverse('worker_create'),
{'name': 'test worker', 'phone': '01207199086', 'branch': branch.id})
print(response)
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'رقم الهاتف مستخدم من قبل')
def test_post_request_valid_data(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test branch1234')
name = 'new valid name'
response = self.client.post(reverse('worker_create'),
{'name': name, 'branch': branch.id, 'phone': '0151951115'})
self.assertEqual(response.status_code, 302)
Important
I noticed when I added print(response) that it gives me HttpResponseRedirect not just HttpResponse what means there is no context given. Why it using redirect in here!?
In your test test_post_request_duplicated_phone, you make a POST request to the worker_create, and you expect to retrieve an error, because there exists already a record for the given phone.
The documentation on tests [Django-doc] however mentions that:
A TestCase, on the other hand, does not truncate tables after a test. Instead, it encloses the test code in a database transaction that is rolled back at the end of the test. This guarantees that the rollback at the end of the test restores the database to its initial state.
So that means that, unless you implement some "tricks" to prevent this, the side-effects that one test has (on the database) will be gone when you enter a second test. This makes sense, since reordering the tests should not result in a different outcome.
You can however create such Worker object in your test in advance, and thus make sure that the test will indeed error:
def test_post_request_duplicated_phone(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
Worker.objects.create(name=" just a name ", phone='01207199086', branch=branch)
response = self.client.post(reverse('worker_create'),
{'name': 'test worker', 'phone': '01207199086', 'branch': branch.id})
print(response)
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'رقم الهاتف مستخدم من قبل')

Django testing an update post

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.)

How to generate list of response messages in Django REST Swagger?

I have upgraded Django REST Framework to 3.5.0 yesterday because I need nice schema generation.
I am using Django REST Swagger to document my API but don't know how to list all possible response messages that an API endpoint provides.
It seems that there is automatic generation of success message corresponding to the action my endpoint is performing.
So POST actions generate 201 response code, without any description.
How would I go about adding all the response messages that my endpoint provides and give them some descriptions?
I am using
djangorestframework==3.5.0
django-rest-swagger==2.0.7
Ah, Finally got it.
But! This is hack on hack - and probably drf + drf swagger not support that; Basically the problem is not connected to the drf and drf swagger code, rather the openapi codec, see yourself:
def _get_responses(link):
"""
Returns minimally acceptable responses object based
on action / method type.
"""
template = {'description': ''}
if link.action.lower() == 'post':
return {'201': template}
if link.action.lower() == 'delete':
return {'204': template}
return {'200': template}
The above code can be found at: openapi_codec/encode.py - github
This is not connected in any way with drf or drf swagger - just for each link (eg.: GET /api/v1/test/) create a template with empty description.
Of course there's a possibility to overcome this issue. But as I said - this is hack on hack :) I will share an example with you:
docs_swagger.views.py
from rest_framework import exceptions
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_swagger import renderers
from docs_swagger.schema_generator import CustomSchemaGenerator
def get_swagger_view(title=None, url=None):
"""
Returns schema view which renders Swagger/OpenAPI.
(Replace with DRF get_schema_view shortcut in 3.5)
"""
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
generator = CustomSchemaGenerator(title=title, url=url) # this is altered line
schema = generator.get_schema(request=request)
if not schema:
raise exceptions.ValidationError(
'The schema generator did not return a schema Document'
)
return Response(schema)
return SwaggerSchemaView.as_view()
What I do in the CustomSchemaGenerator is as follows:
docs_swagger.schema_generator.py
import urlparse
import coreapi
from rest_framework.schemas import SchemaGenerator
from openapi_codec import encode
def _custom_get_responses(link):
detail = False
if '{id}' in link.url:
detail = True
return link._responses_docs.get(
'{}_{}'.format(link.action, 'list' if not detail else 'detail'),
link._responses_docs
)
# Very nasty; Monkey patching;
encode._get_responses = _custom_get_responses
class CustomSchemaGenerator(SchemaGenerator):
def get_link(self, path, method, view):
"""
Return a `coreapi.Link` instance for the given endpoint.
"""
fields = self.get_path_fields(path, method, view)
fields += self.get_serializer_fields(path, method, view)
fields += self.get_pagination_fields(path, method, view)
fields += self.get_filter_fields(path, method, view)
if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method, view)
else:
encoding = None
description = self.get_description(path, method, view)
if self.url and path.startswith('/'):
path = path[1:]
# CUSTOM
data_link = coreapi.Link(
url=urlparse.urljoin(self.url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
description=description
)
data_link._responses_docs = self.get_response_docs(path, method, view)
return data_link
def get_response_docs(self, path, method, view):
return view.responses_docs if hasattr(view, 'responses_docs') else {'200': {
'description': 'No response docs definition found.'}
}
And finally:
my_view.py
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
responses_docs = {
'get_list': {
'200': {
'description': 'Return the list of the Test objects.',
'schema': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': {
'type': 'integer'
}
}
}
}
},
'404': {
'description': 'Not found',
'schema': {
'type': 'object',
'properties': {
'message': {
'type': 'string'
}
}
},
'example': {
'message': 'Not found.'
}
}
},
'get_detail': {
'200': {
'description': 'Return single Test object.',
'schema': {
'type': 'object',
'properties': {
'id': {
'type': 'integer'
}
}
}
},
'404': {
'description': 'Not found.',
'schema': {
'type': 'object',
'properties': {
'message': {
'type': 'string'
}
}
},
'example': {
'message': 'Not found.'
}
}
}
}
I consider this more like fun instead of a real solution. The real solution is probably impossible to achieve at the current state. Maybe you should ask the creators of drf swagger - do they have plans to support responses?
Anyway, the swagger UI:
Happy coding :)