I am working on a small project and I want to be able to test the post methods, saving to the database if the email does not exist, and rendering to index.html for the view below. I've looked at YouTube videos and blog tutorials and I can't seem to find the answer that I am looking for. I have included the Model, View, and my current tests.
Model:
class EmailList(models.Model):
email = models.TextField(unique=True)
View:
def home(request):
# Join Email List
if request.method == 'POST':
if request.POST.get('email'):
email = request.POST.get('email')
if not EmailList.objects.filter(email=email).exists():
emailInput = EmailList()
emailInput.email = email
emailInput.save()
return render(request, 'email/index.html', {})
else:
return render(request, 'email/index.html', {})
Test I have so far:
from django.test import TestCase
from email.models import EmailList
class HomeViewTest(TestCase):
#classmethod
def setUpTestData(cls):
# Create a new email to test
EmailList.objects.create(email="test1#example.com")
def test_home_view_url_exists_at_desired_location(self):
response = self.client.get('')
self.assertEqual(response.status_code, 200)
def test_home_view_post_request_method(self):
response = self.client.post('', {'email' : 'test1#example.com'})
self.assertEqual(response.status_code, 200)
def test_home_view_save_email(self):
self.assertEqual(EmailList.objects.count(), 1)
You should not create an instance of EmailList in your setUpTestData method, because you want to check if your view correctly creates a new instance, right now you manually create a new one before testing. Try this:
from django.test import TestCase
from tasckq.models import EmailList
class HomeViewTest(TestCase):
def test_home_view(self):
# Tests the get method
response = self.client.get('')
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# Tests the post method
email = 'test1#example.com'
response = self.client.post('', {'email' : email})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# A new EmailList instance has been successfully created
self.assertTrue(EmailList.objects.filter(email=email).exists())
response = self.client.post('', {'email' : email})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# A new EmailList instance has not been created because the email was already in use
self.assertEqual(EmailList.objects.count(), 1)
Related
I have a TestCase for some views, but I am also trying to incorporate tests for my post admin group. They have permissions to post.change_post and post.delete_post, just like the user can.
Here is a little code snippet,
from django.contrib.auth import get_user_model
from django.test import TestCase, Client
from django.urls import reverse
from fixtureless.factory import create
from .models import Post
class TestPostViews(TestCase):
def setUp(self):
super().setUp()
self.user = get_user_model().objects.create(username='test_user')
self.form = create(Post, {'title': 'test_title'})
self.client = Client()
def test_update_post(self):
"""
Test UpdateView updates users post
"""
self.client.force_login(user=self.user)
response = self.client.put(reverse('post:update', kwargs={'pk': self.form.pk}),
{'title': 'testing'})
self.assertEqual(response.status_code, 200)
# reloads a models value from the database
self.form.refresh_from_db()
self.assertEqual(self.form.title, 'test_title')
def test_delete_confirm_page(self):
"""
Test DeleteView takes user to confirmation page
"""
self.client.force_login(user=self.user)
response = self.client.get(reverse('post:delete', kwargs={'pk': self.form.pk}))
self.assertContains(response, 'delete')
def test_delete_post(self):
"""
Test DeleteView deletes a post
"""
self.client.force_login(user=self.user)
response = self.client.post(reverse('post:delete', kwargs={'pk': self.form.pk}))
self.assertRedirects(response, reverse('post:list'))
self.assertFalse(Post.objects.filter(pk=self.form.pk).exists())
Any tips on this? Should I create a separate TestCase for the post admin, or is there a way to add this in the initial setUp() method?
I think I am getting a little closer with this snippet...
class TestPostViews(TestCase):
def setUp(self):
super().setUp()
self.user1 = get_user_model().objects.create(username='test_user1')
self.user2 = get_user_model().objects.create(username='test_user2',
password='test_pw')
self.form = create(Post, {'user': self.user1, 'title': 'test_title'})
self.client = Client()
# Group setup
group_name = "post admin"
self.group = Group(name=group_name)
self.group.save()
def test_admin_can_delete(self):
self.user2.groups.add(self.group)
self.user2.save()
self.client.login(username='test_user2', password='test_pw')
response = self.client.post(reverse('posts:delete', kwargs={'pk': self.form.pk}))
self.assertRedirects(response, reverse('posts:list'))
still getting an error
Failure
Expected :'/accounts/login/?next=%2Fpost%2Fdelete%2F2143257160%2F'
Actual :'/post/list/'
So I'm doing TDD with Django and I'm stuck on the following problem.
Test method for class from TestCase
def test_home_page_can_save_POST_request(self):
request = HttpRequest
request.method = 'POST'
request.POST['item_text'] = 'A new list item'
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
And I'm getting error as:
request.POST['item_text'] = 'A new list item'
AttributeError: type object 'HttpRequest' has no attribute 'POST'
But HttpRequest has the attribute 'POST' as per Django docs.
Thansk guys!
You forgot the parentheses after HttpRequest :)
That's why django is saying HttpRequest has no attribute 'POST'
You should consider using RequestFactory for your test cases.
Example from the docs:
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
When testing, if you need a request object, you can also use the request factory to generate one
Reference from the docs:
https://docs.djangoproject.com/en/1.11/topics/testing/advanced/
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
In your case, your test could look like this:
from django.test import RequestFactory
...
def test_home_page_can_save_POST_request(self):
factory = RequestFactory()
response = factory.post('PATH_TO_YOUR_VIEW', data={
'item_text': 'A new list item',
})
self.assertIn('A new list item', response.content.decode())
I have two solution.
1.- Create request object with RequestFactory and calling at views.
def test_home_page_can_save_a_POST_request(self):
request = self.factory.post("/", data={'item_text': 'A new list item',})
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
2.- Or use the response of RequestFactory().
def test_home_page_can_save_a_POST_request(self):
response = self.factory.post("/", data={'item_text': 'A new list item',})
self.assertIn('A new list item', response.readlines()[3].decode())
This is my test:
class PageTests(APITestCase):
def setUp(self):
Location.objects.create(locationName = 'Location of Mine', LocationCode = 'LOM')
User.objects.create(username='b', password='b', email='b#hotmail.com')
def test_create_page(self):
"""
Ensure only authenticated users can create a new page object.
"""
url = reverse('page-list')
# See if unauthenticated unadmin users can create a page (they shouldn't.)
data = {'location': 1, 'pageName': 'Test Page 1', 'pageDescription': 'This is the first test page', 'pageCode': 'TP1'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
# See if authenticated users can create a page (they should).
print(User.objects.get().username)
self.client.login(username='b', password='b')
response = self.client.post(url, data, format='json')
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
This is my views.py / viewset:
class IsAuthenticated(permissions.BasePermission):
def has_permission(self, request, view):
print('here!!!!!!!!!!!!')
print(request.user)
return request.user.is_authenticated()
class pageViewSet(viewsets.ModelViewSet):
queryset = Page.objects.all()
serializer_class = PageSerializer
permission_classes = (IsAuthenticated,)
The problem is, even after I log the user in by doing self.client.login(username='b', password='b') it still raises a 403 error when posting. This is what gets printed:
here!!!!!!!!!!!!
AnonymousUser
b
here!!!!!!!!!!!!
AnonymousUser
{'detail': 'Authentication credentials were not provided.'}
As you can see, Django does see the user object (because it prints 'b') but the user does not get signed in for some reason and is still an AnonymousUser. Now, when I change my setup to this:
def setUp(self)
url = reverse('user-list')
# Create the user using the API.
data = {'username': 'b', 'password': 'b', 'email': 'a#hotmail.com', 'location': '1'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
and then log the user in, it work perfectly fine and the test doesn't raise any errors. Any idea why it raises errors when creating the user using User.objects.create()?
I've used similar code before in other unittest classes (creating the user using the ORM and then signing him in) and it works. I'm not sure why it's not working here.
Edit: Also, if I create the user and make him a super user and log him in, like so:
User.objects.create_superuser(username='a', password='a', email='a#hotmail.com')
it works as well.
Found the answer. I had to create the user by doing this:
User.objects.create_user()
and not this:
User.objects.create()
I am testing a view and my test looks like:
def test_profile(self, user_id):
user = User.objects.create_user(username="myusername", password="password", email="abc#testmail.com")
self.client.user = user
print(user.id)
request = self.client.get("/account/profile/{}/".format(user_id), follow=True)
self.assertEqual(request.status_code, 200)
Here my profile view has a login_required decorator. How can I set user to request.user?
I was trying to do the same myself but found out that Django Test Client does not set the user in the request and it is not possible to set request.user while using Client any other way. I used RequestFactory to that.
def setUp(self):
self.request_factory = RequestFactory()
self.user = User.objects.create_user(
username='javed', email='javed#javed.com', password='my_secret')
def test_my_test_method(self):
payload = {
'question_title_name': 'my first question title',
'question_name': 'my first question',
'question_tag_name': 'first, question'
}
request = self.request_factory.post(reverse('home'), payload)
request.user = self.user
response = home_page(request)
More about request factory here
Try this:
from django.test import TestCase, Client
from django.contrib.auth.models import User
class YourTestCase(TestCase):
def test_profile(self, user_id):
user = User.objects.create(username='testuser')
user.set_password('12345')
user.save()
client = Client()
client.login(username='testuser', password='12345')
response = client.get("/account/profile/{}/".format(user.id), follow=True)
self.assertEqual(response.status_code, 200)
Here, I first create the user and set the login credentials for the user. Then I create a client and login with that user. So in your views.py, when you do request.user, you will get this user.
If you use django.test you can do something like that:
self.client.force_login(user)
This works:
self.client.force_authenticate(user=user)
If you have a response, you can access response.context['user'].
If you need a response object, just call any view that will create a context, e.g. response = self.client.get('/').
I have started using Django's testing framework, and everything was working fine until I started testing authenticated pages.
For the sake of simplicity, let's say that this is a test:
class SimpleTest(TestCase):
def setUp(self):
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
c = Client()
print c.login(username='temporary', password='temporary')
response = c.get('/users/secure/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
After I run this test, it fails, and I see that printing return value of login() returns True, but response.content gets redirected to login page (if login fails authentication decorator redirects to login page). I have put a break point in decorator that does authentication:
def authenticate(user):
if user.is_authenticated():
return True
return False
and it really returns False. Line 4 in test_secure_page() properly retrieves user.
This is the view function:
#user_passes_test(authenticate, login_url='/users/login')
def secure(request):
user = request.user
return render_to_response('secure.html', {'email': user.email})
Of course, if I try to login through application (outside of test), everything works fine.
The problem is that you're not passing RequestContext to your template.
Also, you probably should use the login_required decorator and the client built in the TestCase class.
I'd rewrite it like this:
#views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model
#login_required(login_url='/users/login')
def secure(request):
user = request.user
return render(request, 'secure.html', {'email': user.email})
#tests.py
class SimpleTest(TestCase):
def setUp(self):
User = get_user_model()
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
User = get_user_model()
self.client.login(username='temporary', password='temporary')
response = self.client.get('/manufacturers/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
It can often be useful to use a custom auth backend that bypassess any sort of authentication during testing:
from django.contrib.auth import get_user_model
class TestcaseUserBackend(object):
def authenticate(self, testcase_user=None):
return testcase_user
def get_user(self, user_id):
User = get_user_model()
return User.objects.get(pk=user_id)
Then, during tests, add yourapp.auth_backends.TestcaseUserBackend to your AUTHENTICATION_BACKENDS:
AUTHENTICATION_BACKENDS = [
"akindi.testing.auth_backends.TestcaseUserBackend",
]
Then, during tests, you can simply call:
from django.contrib.auth import login
user = User.objects.get(…)
login(testcase_user=user)
Token based authentication:
I was in same situation. I found solution in which actually I did generate a user for login purpose in setUp method. Then later in the test methods, I tried to get the token and passed it along with request data.
setUp:
create a user
self.pravesh = User.objects.create(
email='psj.aaabbb#gmail.com',
first_name='Pravesh',
last_name='aaabbb',
phone='5456165156',
phonecountrycode='91'
)
set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)
test_method:
create client
client.login(email=self.pravesh.email, password=self.password)
get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
pass login information
response = client.post(
reverse('account:post-data'),
data = json.dumps(self.data),
HTTP_AUTHORIZATION='Token {}'.format(token),
content_type = 'application/json'
)