Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing for views, models, and forms in Django Application #11

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,5 @@ dmypy.json
# End of https://www.gitignore.io/api/django

#Virtual environment
venv/
venv/
my-project-env
18 changes: 18 additions & 0 deletions catalog/migrations/0005_alter_book_maximum_retail_price.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0 on 2022-06-28 21:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('catalog', '0004_bookcopy_due_back'),
]

operations = [
migrations.AlterField(
model_name='book',
name='maximum_retail_price',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
2 changes: 1 addition & 1 deletion catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Book(models.Model):
author = models.CharField(max_length=100)
genre = models.CharField(max_length=100)
description = models.TextField(null=True)
maximum_retail_price = models.PositiveIntegerField()
maximum_retail_price = models.PositiveIntegerField(null=True, blank=True)
rating = models.FloatField(default=0.0)

class Meta:
Expand Down
3 changes: 0 additions & 3 deletions catalog/tests.py

This file was deleted.

Empty file added catalog/tests/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions catalog/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import datetime

from django.test import TestCase
from django.utils import timezone

from catalog.forms import RenewBookForm

class RenewBookFormTest(TestCase):
def test_renew_form_date_field_label(self):
form = RenewBookForm()
self.assertTrue(form.fields['renewal_date'].label is None or form.fields['renewal_date'].label == 'renewal date')

def test_renew_form_date_field_help_text(self):
form = RenewBookForm()
self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).')

def test_renew_form_date_in_past(self):
date = datetime.date.today() - datetime.timedelta(days=1)
form = RenewBookForm(data={'renewal_date': date})
self.assertFalse(form.is_valid())

def test_renew_form_date_too_far_in_future(self):
date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1)
form = RenewBookForm(data={'renewal_date': date})
self.assertFalse(form.is_valid())

def test_renew_form_date_today(self):
date = datetime.date.today()
form = RenewBookForm(data={'renewal_date': date})
self.assertTrue(form.is_valid())

def test_renew_form_date_max(self):
date = timezone.localtime() + datetime.timedelta(weeks=4)
form = RenewBookForm(data={'renewal_date': date})
self.assertTrue(form.is_valid())
35 changes: 35 additions & 0 deletions catalog/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.test import TestCase

# Create your tests here.
from catalog.models import Book

class BookModelTest(TestCase):
@classmethod
def setUpTestData(cls):
# Set up non-modified objects used by all test methods
Book.objects.create(title='Rich Dad Poor Dad', author='Robert Kiyoski')

def test_title_label(self):
book = Book.objects.get(id=1)
field_label = book._meta.get_field('title').verbose_name
self.assertEqual(field_label, 'title')

def test_author_label(self):
book = Book.objects.get(id=1)
field_label = book._meta.get_field('author').verbose_name
self.assertEqual(field_label, 'author')

def test_genre_max_length(self):
book = Book.objects.get(id=1)
max_length = book._meta.get_field('genre').max_length
self.assertEqual(max_length, 100)

def test_object_name_is_title_by_author(self):
book = Book.objects.get(id=1)
expected_object_name = f'{book.title} by {book.author}'
self.assertEqual(str(book), expected_object_name)

def test_get_absolute_url(self):
book = Book.objects.get(id=1)
# This will also fail if the urlconf is not defined.
self.assertEqual(book.get_absolute_url(), '/catalog/book/1')
258 changes: 258 additions & 0 deletions catalog/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
from django.test import TestCase

# Create your tests here.
from django.test import TestCase
from django.urls import reverse

from catalog.models import Book

class BookListViewTest(TestCase):
@classmethod
def setUpTestData(cls):
# Create 5 books for pagination tests
number_of_books = 5

for book_id in range(number_of_books):
Book.objects.create(
title=f'Art of Making Money {book_id}',
author=f'Ronald',
)

def test_view_url_exists_at_desired_location(self):
response = self.client.get('/catalog/books/')
self.assertEqual(response.status_code, 200)

def test_view_url_accessible_by_name(self):
response = self.client.get(reverse('books'))
self.assertEqual(response.status_code, 200)

def test_view_uses_correct_template(self):
response = self.client.get(reverse('books'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'catalog/book_list.html')

def test_pagination_is_three(self):
response = self.client.get(reverse('books'))
self.assertEqual(response.status_code, 200)
self.assertTrue('is_paginated' in response.context)
self.assertTrue(response.context['is_paginated'] == True)
self.assertEqual(len(response.context['book_list']), 3)

def test_lists_all_books(self):
# Get second page and confirm it has (exactly) remaining 3 items
response = self.client.get(reverse('books')+'?page=2')
self.assertEqual(response.status_code, 200)
self.assertTrue('is_paginated' in response.context)
self.assertTrue(response.context['is_paginated'] == True)
self.assertEqual(len(response.context['book_list']), 2)


import datetime

from django.utils import timezone
from django.contrib.auth.models import User # Required to assign User as a borrower

from catalog.models import BookCopy, Book

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to have import at top (one place). here you are also reimporting book that should not be done.

class LoanedBookInstancesByUserListViewTest(TestCase):
def setUp(self):
# Create two users
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')

test_user1.save()
test_user2.save()

# Create a book
test_book = Book.objects.create(
title='Book Title',
author='Test Author',
genre='Horror'

)

test_book.save()

# Create 30 BookInstance objects
number_of_book_copies = 30
for book_copy in range(number_of_book_copies):
return_date = timezone.localtime() + datetime.timedelta(days=book_copy%5)
the_borrower = test_user1 if book_copy % 2 else test_user2
status = True
BookCopy.objects.create(
book=test_book,
due_back=return_date,
borrower=the_borrower,
status=status,
)

def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('my-borrowed'))
self.assertRedirects(response, '/accounts/login/?next=/catalog/mybooks/')

def test_logged_in_uses_correct_template(self):
login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('my-borrowed'))

# Check our user is logged in
self.assertEqual(str(response.context['user']), 'testuser1')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)

# Check we used correct template
self.assertTemplateUsed(response, 'catalog/bookcopy_list_borrowed_user.html')

def test_only_borrowed_books_in_list(self):
login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('my-borrowed'))

# Check our user is logged in
self.assertEqual(str(response.context['user']), 'testuser1')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)

# Check that initially we don't have any books in list (none on loan)
self.assertTrue('bookcopy_list' in response.context)
self.assertEqual(len(response.context['bookcopy_list']), 0)

# Now change all books to be on loan
books = BookCopy.objects.all()[:10]

for book in books:
book.status = 'o'
book.save()

# Check that now we have borrowed books in the list
response = self.client.get(reverse('my-borrowed'))
# Check our user is logged in
self.assertEqual(str(response.context['user']), 'testuser1')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)

self.assertTrue('bookcopy_list' in response.context)

# Confirm all books belong to testuser1 and are on loan
for bookitem in response.context['bookcopy_list']:
self.assertEqual(response.context['user'], bookitem.borrower)
self.assertEqual(bookitem.status, 'o')

def test_pages_ordered_by_due_date(self):
# Change all books to be on loan
for book in BookCopy.objects.all():
book.status= False
book.save()

login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('my-borrowed'))

# Check our user is logged in
self.assertEqual(str(response.context['user']), 'testuser1')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)

# Confirm that of the items, only 10 are displayed due to pagination.
self.assertEqual(len(response.context['bookcopy_list']), 10)

last_date = 0
for book in response.context['bookcopy_list']:
if last_date == 0:
last_date = book.due_back
else:
self.assertTrue(last_date <= book.due_back)
last_date = book.due_back


## Testing View with Forms
import uuid

from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned.

class RenewBookInstancesViewTest(TestCase):
def setUp(self):
# Create a user
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')

test_user1.save()
test_user2.save()


# Create a book
test_book = Book.objects.create(
title='Book Title',
author='test_author',
)

test_book.save()

# Create a BookInstance object for test_user1
return_date = datetime.date.today() + datetime.timedelta(days=5)
self.test_bookinstance1 = BookCopy.objects.create(
book=test_book,
due_back=return_date,
borrower=test_user1,
status='o',
)

# Create a BookInstance object for test_user2
return_date = datetime.date.today() + datetime.timedelta(days=5)
self.test_bookinstance2 = BookCopy.objects.create(
book=test_book,
due_back=return_date,
borrower=test_user2,
status='o',
)

def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
# Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.startswith('/accounts/login/'))

def test_forbidden_if_logged_in_but_not_correct_permission(self):
login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
self.assertEqual(response.status_code, 403)

def test_logged_in_with_permission_borrowed_book(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance2.pk}))

# Check that it lets us login - this is our book and we have the right permissions.
self.assertEqual(response.status_code, 200)

def test_logged_in_with_permission_another_users_borrowed_book(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))

# Check that it lets us login. We're a librarian, so we can view any users book
self.assertEqual(response.status_code, 200)

def test_HTTP404_for_invalid_book_if_logged_in(self):
# unlikely UID to match our bookinstance!
test_uid = uuid.uuid4()
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid}))
self.assertEqual(response.status_code, 404)

def test_uses_correct_template(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
self.assertEqual(response.status_code, 200)

# Check we used correct template
self.assertTemplateUsed(response, 'catalog/book_renew_librarian.html')

def test_form_renewal_date_initially_has_date_three_weeks_in_future(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}))
self.assertEqual(response.status_code, 200)

date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3)
self.assertEqual(response.context['form'].initial['renewal_date'], date_3_weeks_in_future)

def test_redirects_to_all_borrowed_book_list_on_success(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2)
response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future})
self.assertRedirects(response, reverse('all-borrowed'))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should use a linter also to avoid lint issue like missing newline an end or any other lint issues.