Django AttributeError: object has no attribute 'assertContains' - django

I'm trying Django test fixtures from the official documentation, but my test class can't find assertContains.
from django.utils import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.post('/register',
{'username': '123',
'password': '123',
follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Logout")
self.assertNotContains(response, "Login")

assertContains is a Django specific feature, not Python specific. Thus, make sure the test class is subclassed from TestCase in django.test, not TestCase in (python) unittest.
i.e.
from django.test import TestCase
class SimpleTest(TestCase):
self.assertContains(response, "Logout")

That's it (the first answer), In my case, i'm using django test_plus, and thats was the issue:
from django.test import TestCase
class BlogTestCase(TestCase):
def test_blog(self):
response = self.get('blog_detail', self.blog.slug)
Change the import to
from test_plus.test import TestCase

Related

DRF APIClient Delete data arrives in request.data, not request.query_params

I use DRF's APIClient to write automated tests. And while is was writing the first delete test, I found it very strange that the data passed through arrived in request.data, while if I use Axios or Postman, it always arrives in request.query_params.
Any explanation as to why this is, and preferably a method to use APIClient.Delete while the data arrives in query_params would be great!
My test:
import pytest
from rest_framework.test import APIClient
#pytest.fixture()
def client():
client = APIClient()
client.force_authenticate()
yield client
class TestDelete:
def test_delete(client):
response = client.delete('/comment', data={'team': 0, 'id': 54})
And my views
from rest_framework.views import APIView
class Comments(APIView):
def delete(self, request):
print(request.query_params, request.data)
>>> <QueryDict: {}> <QueryDict: {'team': ['0'], 'id': ['54']}>
Looked into DRF's APIClient. Feeding towards params doesn't seem to help. The delete method doesn't seem to have direct arguments that could help as well. So I'm a bit stuck.
Though some good options have been proposed, they didn't work with DRF's APIView. I ended up using urllib and encode it manually:
import pytest
from urllib.parse import urlencode
from rest_framework.test import APIClient
#pytest.fixture()
def client():
client = APIClient()
client.force_authenticate()
yield client
class TestDelete:
def test_delete(client):
response = client.delete(f'/comment{urlencode(data)}')

Fixtures are not meant to be called directly

I'm using Django 3.0.5, pytest 5.4.1 and pytest-django 3.9.0. I want to create a fixture that returns a User object to use in my tests.
Here is my conftest.py
import pytest
from django.contrib.auth import get_user_model
#pytest.fixture
def create_user(db):
return get_user_model().objects.create_user('user#gmail.com', 'password')
Here is my api_students_tests.py
import pytest
from rest_framework.test import APITestCase, APIClient
class StudentViewTests(APITestCase):
user = None
#pytest.fixture(scope="session")
def setUp(self, create_user):
self.user = create_user
def test_create_student(self):
assert self.user.email == 'user#gmail.com'
# other stuff ...
I keep getting the following error
Fixture "setUp" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.
I read and read again this previous question but I cannot find out a solution. Furthermore, in that question the fixture wasn't returning nothing, while in my case it should return an object (don't know if it can make any difference)
Just skip the setUp:
#pytest.fixture(scope='session')
def create_user(db):
return get_user_model().objects.create_user('user#gmail.com', 'password')
class StudentViewTests(APITestCase):
def test_create_student(self, create_user):
assert user.email == 'user#gmail.com'
# other stuff ...

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.

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)

Django pytest-selenium functional tests

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