Django pytest-selenium functional tests - django

I've created the tests folder, written my first test that should open a browser, point to a page and login, then go to home page.
Test run and fail, as expected, but I can't find out why.
browser should be available, pytest-selenium is installed by pip.
import pytest
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_homepage(self):
request = self.client.get('/new')
request.user = self.user
self.assertEqual(request.status_code, 200)
def test_login(self):
request = self.client.get('/per/login')
username_field = self.browser.find_element_by_name('username')
username_field.send_keys('peppa')
password_field = self.browser.find_element_by_name('password')
password_field.send_keys('pig')
password_field.send_keys(Keys.RETURN)
test_homepage()
> username_field = self.browser.find_element_by_name('username')
E AttributeError: 'CreaPageTest' object has no attribute 'browser'
tests/test_ore_app_views.py:27: AttributeError
what am I missing?
Any advice to examples of this kind of test is really appreciated.

You should configure self.browser inside setUp function. You are also missing an import for Keys. Code should be like this.
import pytest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.browser = webdriver.Firefox()
Also please refer to the docs, here http://selenium-python.readthedocs.org/getting-started.html

Related

Access basket from view called with unit test in Django Oscar

How do I attach a Django Oscar basket that has been created in a unit test to the request object?
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework.response import Response
from rest_framework.views import APIView
class BasketAPIAddView(LoginRequiredMixin, APIView):
"""
Update basket via REST API.
"""
def delete(self, request, format=None):
#
# cannot access `request.basket` here
#
return Response({})
# tests.py
from django.contrib.auth import get_user_model
from django.urls import reverse
from oscar.test.factories import create_basket
from rest_framework.test import APITestCase
User = get_user_model()
class BasketAPITests(APITestCase):
"""
Basket view test cases.
"""
def test_remove_basket_line(self):
basket = create_basket()
basket.owner = User.objects.create_user('user', password='pass')
basket.save()
self.client.login(username='user', password='password')
self.client.delete(reverse('delete-basket'))
You can do something like:
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APIRequestFactory
from oscar.test.factories import create_basket
User = get_user_model()
class BasketAPITests(APITestCase):
def test_remove_basket_line(self):
"""Check the method BasketAPIAddView.delete works as should."""
# Create the request, note the .delete which is the operation (BasketAPIAddView.delete)
request = APIRequestFactory().delete(reverse('<your url name>'))
# Create the basket
basket = create_basket()
basket.owner = User.objects.create_user('user', password='pass')
basket.save()
# Attach the basket to the request obj
request.basket = create_basket()
# Call the endpoint, with the proper request obj
response = BasketAPIAddView.as_view()(request)
# Some verifications
...
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
...
The key is to use a request factory and have the object at your will (adding what you need).
For more details you can take a look to https://www.django-rest-framework.org/api-guide/testing/
For normal requests oscar.apps.basket.middleware.BasketMiddleware adds the basket to the request, so it is possible you need to check the settings.MIDDLEWARE that your test project is using, or that the client provided by APITestCase is running middleware.
If you're developing an API you might also want to look at the way django-oscar-api handles session management and middleware.

Selenium cleaning database in Django Application

I have a Django Application and I'm trying to create some selenium tests for it. The problem is that every time I run the test my database gets cleaned.
This is my test:
import time
from django.contrib.auth.models import User
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class CrewTest(StaticLiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Chrome('chromedriver')
super(CrewTest, self).setUp()
self.user = User.objects.create_user(username='user',
email=None,
password='password')
def test_register(self):
selenium = self.selenium
selenium.get('http://127.0.0.1:8000/static/myapp.html')
name = selenium.find_element_by_id('username')
password = selenium.find_element_by_id('password')
submit = selenium.find_element_by_id('submit')
name.send_keys('user')
password.send_keys('password')
submit.send_keys(Keys.RETURN)
selenium.page_source
time.sleep(1)
assert 'Welcome user' in selenium.page_source
The test is successful, but my database is empty after I run it. Should I have a separate database for selenium tests? Or can I just disable cleaning the database after the test.

Django View Testing Returning 301 or not found

I'm trying to test the response code of a view, but I'm either getting a 301 or does not exist.
urls.py
...
url(r'^myview/(?P<view_id>.*)/$', view_myview.index, name='myview'),
...
Test code 1:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123')
self.assertEqual(response.status_code, 200)
The above code gives:
AssertionError: 301 != 200
Test code 2:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123/')
self.assertEqual(response.status_code, 200)
The above code gives:
Mymodel matching query does not exist.
All I want to do is simple testing of my views to ensure they aren't throwing an error code, but I can't seem to find the right way to do it and I've tried many, many suggestions from the internets. Is there a different way to pass in view_id? What if I also want to throw in some query parameters?
EDIT: Updating to show the workaround I've used to accomplish what I'm trying to do, as horrible as it may be. I found that using dumpdata and fixtures took FOREVER.
from django.test import TestCase
from django.test import Client
import os
from . import urls_to_test # just a simple list of strings
class SimpleTest(TestCase):
""" Simply test if views return status 200 """
def setUp(self):
self.client = Client()
print('Dumping production database...')
os.system("sudo mysqldump mydb > /tmp/mydb.sql")
print('Loading production data into test database...')
os.system("sudo mysql test_mydb < /tmp/mydb.sql")
os.system("sudo rm -rf /tmp/mydb.sql")
def test_details(self):
for u in urls_to_test.test_urls:
print('Testing {}'.format(u))
response = self.client.get(u)
self.assertEqual(response.status_code, 200)
print('{} URLs tested!'.format(len(urls_to_test.test_urls)))
The first one doesn't work because Django is redirecting to the version with a final slash.
The second one tells you exactly why it doesn't work: you haven't created an item with id 123 - or indeed any items at all - within the test.
Consider creating object before testing its existance:
import unittest
from django.test import Client
from app.models import YourModel
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.obj = YourModel.objects.create(*your object goes here*)
def test_details(self):
response = self.client.get('/myview/123/') # It may be not /123/. It depends on how you generate url for model
self.assertEqual(response.status_code, 200)

Unregistering default Django apps from admin breaks tests

In one of my apps/admin.py I have
from django.contrib import admin
from django.contrib.sites.models import Site
from django.contrib.redirects.models import Redirect
from mezzanine.generic.models import ThreadedComment
from mezzanine.conf.models import Setting
admin.site.unregister(Site)
admin.site.unregister(Redirect)
admin.site.unregister(ThreadedComment)
admin.site.unregister(Setting)
This causes them to be removed from the admin like I want, and the application works fine.
However, when I run my tests via nose, I get this error raise NotRegistered('The model %s is not registered' % model.__name__)
NotRegistered: The model Site is not registered Which I assume is because it is trying to unregister something which is already unregistered. This is an example of a test that is failing:
class TestRegistration(TestCase):
def setUp(self):
self.client = Client()
email = ConfirmedEmail(email='test.com',company='Test Industries')
email.save()
def test_landing(self):
response = self.client.get(reverse('home'))
self.assertEqual(response.status_code, 200)

Accessing the request.user object when testing Django

I'm trying to access the request.user object when testing my app using django's client class.
from django.test import TestCase
from django.test.client import Client
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.client.login( username="foo", password="bar")
def test_one(self):
response = self.client.get("/my_profile/")
self.fail( response.request.user )
This will obviously fail, but it fails because response.request doesn't have a user attribute.
AttributeError: 'dict' object has no attribute 'user'
Is there any way to access the user object from the client instance? I want to do this in order to check if some test user's data is properly rendered. I can try to tackle this by setting up all the necessary info during setUp, but before I do that I wanted to check if I'm not just missing something.
This may seem a roundabout way of doing it but it can be useful.
Use RequestFactory, its pretty straightforward. It imitates the request object.
from django.test import TestCase, RequestFactory
from django.test.client import Client
from django.contrib.auth.models import User
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='foo', email='foo#bar',
password='bar')
def test_one(self):
self.client.login( username="foo", password="bar")
request = self.factory.get("/my_profile/")
request.user = self.user
#rest of your code
def tearDown(self):
self.user.delete()
I hope that was helpful.
Use response.context['user'].
User is automatically available in the template context if you use RequestContext. See auth data in templates doc.
Otherwise i believe you should just query it:
def test_one(self):
response = self.client.get("/my_profile/")
user = User.objects.get(username="foo")
self.fail( user.some_field == False )