How to avoid duplicating test cases in Django? - python

I have written my test cases in two separate test files (e.g. test_1 and test_2). In both of test cases that I am testing my models I have code duplications because of similar processes.
For example, I need to login the user and test the credential.
Sample of code:
import test_data
from django.test import TestCase
from UserData.models import MyModel
from django.contrib.auth.models import User
class UserDataMyModelTestCalls(TestCase):
#classmethod
def setUpTestData(cls):
cls.test_user = User.objects.create_user(test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'])
def test_faulty_login_credentials(self):
self.client.login(username=test_data.faulty_user_data['user_name'], password=test_data.faulty_user_data['password'])
response = self.client.get('/userdata/mymodelurl/', {})
self.assertEqual(response.status_code, 403)
I am using a separate file with user credentials to avoid duplications again.
Sample of test_data file:
test_user_data = {'id': u'1',
'user_name': 'tempUsername',
'password': 'tempPassword',
'email': 'tempEmaily#test.com'}
Update: Adding the UserTests class that I want to use as a common class for all my test cases. I am defining and calling the test through the test_1.py like this:
import UserTests
from django.test import TestCase
class UserDataWayPointTestCalls(TestCase):
testCasesObject = UserTests.UserDataTestCalls()
test_user = testCasesObject.setUpTestData()
response = testCasesObject.test_faulty_login_credentials()
My UserDataTestCalls class is defined like this:
import test_data
from django.test import Client
from django.test import TestCase
from django.contrib.auth.models import User
class UserDataTestCalls(TestCase):
def __init__(self):
self.test_user = None
self.faulty_login_response = None
def setUpTestData(self):
self.client = User.objects.create_user(test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'])
self.client = Client()
return self.client
def test_faulty_login_credentials(self):
self.client.login(username=test_data.faulty_user_data['user_name'],
password=test_data.faulty_user_data['password'])
response = self.client.get('/userdata/mymodelurl/', {})
return response
When I execute the code above I get IntegrityError: (1062, "Duplicate entry 'tempUsername' for key 'username'"). Temporarily I modify the username value to proceed and I get the following error AttributeError: 'UserDataTestCalls' object has no attribute '_testMethodName'.
I tried to create a separate class with name e.g. UserDataTestCalls and include the common parts of my test cases such as User.objects.create_user, self.client.login etc...
Unfortunately I end up getting errors that the database although it said Destroying test database for alias 'default'... on the next run I got username duplications e.g. Duplicate entry 'tempUsername' for key 'username' etc...
When I tried to overcome this problem by changing the username for testing purposes then I got another problem 'NoneType' object has no attribute 'login'.
Which it points that the self.client variable is not binded with the test_user that I am creating.
I tried to search online and find documentation on how to overcome my problem but all the documentation are pointing to use separate scripts for your tests individually, which I can understand if you have different test cases. In my case 90% of my test cases are exactly the same.
So I am sure there is a way to create a user in a separate class and create all my test cases in that class too, so I could call them from a separate test file(s) when I need them.
Can someone point me to the correct direction or provide some links with examples/documentation that I could read from?
Thank you in advance for your time and effort.

Try creating a common test class.
class CreateUserTestCase(TestCase):
def setUpTestData(self):
self.user = User.objects.create_user(
test_data.test_user_data['user_name'],
test_data.test_user_data['email'],
test_data.test_user_data['password'],
)
You want to assign the new user to self.user. Don't replace self.client which should be the test client, not the user. You don't need to do self.client = Client(), the Django test case will take care of this for you.
Then subclass the test case and add your tests.
class UserDataTestCalls(CreateUserTestCase):
def test_faulty_login_credentials(self):
self.client.login(
username=test_data.faulty_user_data['user_name'],
password=test_data.faulty_user_data['password'],
)
response = self.client.get('/userdata/mymodelurl/', {})
return response
From your question, I wasn't sure if test_data is different for each class. If so, you'll have to change this slightly.

Related

Unable to authenticate Client in test

Python 3.10.7, Django 4.1.1, Django REST Framework 3.13.1
I am unable to get the django.test.Client login or force_login methods to work in a django.test.TestCase-derived test class. I'm referring to https://docs.djangoproject.com/en/4.1/topics/testing/tools/
My project seems to work when viewing it in a browser. Unauthenticated DRF views appear as expected, and if I log in through the admin site, protected views also appear as expected. A preliminary version of the front end that will consume this API is able to read data and display it with no problem. The local Django unit test environment uses a local SQLite3 install for data. All tests not requiring authentication are currently passing.
This simplified test class reliably displays the problem:
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from eventsadmin.models import Address
class AddressesViewTest(TestCase):
username = "jrandomuser"
password = "qwerty123"
user = User.objects.filter(username=username).first()
if user:
print("User exists")
else:
user = User.objects.create(username=username)
print("User created")
user.set_password(password)
user.save()
client = Client()
def setUp(self):
if self.client.login(username=self.username, password=self.password):
print("Login successful")
else:
print("Login failed")
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188")
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
First, I was surprised that I had to test for the existence of the User. Even though the test framework emits messages saying it is creating and destroying the test database for each run, after the test has been run once the creation of the User fails with a unique constraint violation on the username. If I don't change the value of username the test as written here consistently emits User exists. This is the only test currently creating/getting a User so I'm sure it's not being created by another test.
The real problem is setUp. It consistently emits Login failed, and test_addresses fails on access permissions (which is correct behavior when access is attempted on that view without authentication). If I set a breakpoint in the last line of setUp, at that point self.client is an instance of django.test.Client, and self.username and self.password have the expected values as set above.
I tried replacing the call to login with self.client.force_login(self.user) but in that case when that line is reached Django raises django.db.utils.DatabaseError: Save with update_fields did not affect any rows. (the stack trace originates at venv/lib/python3.10/site-packages/django/db/models/base.py", line 1001, in _save_table).
What am I doing wrong? How can I authenticate in this context so I can test views that require authentication?
I've been doing a bunch of tests recently and here's how are all of mine create the user inside of the setup, like the block below, give that a shot.
class AddressesViewTest(TestCase):
def setUp(self):
self.username = "jrandomuser"
self.password = "qwerty123"
user = User.objects.create(username=self.username)
user.set_password(self.password)
user.save()
self.client.login(username=self.username, password=self.password)
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188"
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
And actually I don't even login during the setUp because I want to make sure the view has a #login_required, so it do it in the test itself:
class AddressesViewTest(TestCase):
def setUp(self):
self.username = "jrandomuser"
self.password = "qwerty123"
user = User.objects.create(username=self.username)
user.set_password(self.password)
user.save()
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188"
def test_anonymous(self):
response = testObj.client.get(reverse("addresses-list"))
testObj.assertEqual(response.status_code, 302, 'address-list #login_required Missing')
def test_addresses(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
From what I've noticed is that setUp is ran per test. So in my last example the user would be created for anonymous, deleted or reverted, created for test_addresses. So having the user outside of that block is probably leading to the user not being deleted/reverted which is leading to some funky behavior.
And I know the tests say it removed the db every single time, without the --keepdb flag, but I'm starting to doubt that.. cause I've been hitting some weird behavior and it's only after I run the test back-to-back-to-back-to-back.. something is off forsure
A friend with more experience put me onto what seems to be the right track, which is a setUpTestData class method, that gets called only once. I would not have thought of this myself because I imagined classmethod to be similar to static in .NET or Java, but apparently not; I have quite a bit more to learn here.
Mostly all test data creation not specific to a particular test ought to go in setUpTestData, also, he says.
This works:
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
from eventsadmin.models import Address
class AddressesViewTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create(username="jrandomuser")
cls.client = Client()
Address.objects.create(name="White House", address1="1600 Pennsylvania Ave", city="Washington", state="DC", postal_code="37188")
def setUp(self):
self.client.force_login(self.user)
def test_addresses(self):
response = self.client.get(reverse("addresses-list"))
self.assertContains(response, '"name":"White House"')
As #nealium points out, it also makes sense to move the call to login or force_login into the test if there are any tests where you don't want the Client to be authenticated.

Why isn't django.contrib.auth.authenticate() working here?

I'm writing a simple (for now) Django app. I'm having trouble with authentication: I'm trying to use all of the out-of-the-box components, and both in the app itself and in tests, I can't authenticate users. So, for example, here's a test that fails:
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.test import TestCase
[...]
class UserTestCase(TestCase):
def setUp(self):
self.testu = User(username="thename", password="thepassword", first_name="thefirstname")
self.testu.save()
def testAuthenticate(self):
u = authenticate(username="thename", password="thepassword")
self.assertEqual(u.first_name, "thefirstname")
I get an AttributeError:
'NoneType' object has no attribute "first_name".
I think this is because authenticate() is returning None (representing that there is no such user).
This fails whether or not I include the line "self.testu.save()".
I have other tests that pass, so I don't think the problem is with the test infrastructure. I can successfully create users and retrieve their information from the database.
The only mention of User in models.py is:
from django.contrib.auth.models import User
I've read through a lot of documentation but can't figure out what's going on. Can anyone help? Thanks in advance.
You can not create a User object with a password like that. The password needs to be hashed. Therefore, you should use the .set_password(..) method [Django-doc]:
class UserTestCase(TestCase):
def setUp(self):
self.testu = User(username="thename", first_name="thefirstname")
self.testu.set_password("thepassword")
self.testu.save()
# …

Database is cleaned up after each test in pytest-django

What I am trying to achieve.
I am testing rest API in Django project. I want to create test class with test functions that are related (each following test function relies on previous one) - first fail means all failed. In first test function I crete an object with 'post' request. In next test case I want to check that this object actually exists using 'get' request.
How it works
It looks like Django-pytest cleans the database from all records after each test.
It is mentioned in pytest documention: https://pytest-django.readthedocs.io/en/latest/helpers.html#pytest-mark-django-db-request-database-access
Is there any way to change this?
My code:
My conftest.py:
import pytest
#pytest.fixture(scope='session')
def django_db_setup():
from django.conf import settings
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'db_name.sqlite3',
}
#pytest.fixture
def object_data():
return {"some keys": "some values"}
#pytest.fixture
def object_name():
return "some name"
My tests.py:
import pytest
from rest_framework.test import APIClient
from rest_framework import status
#pytest.mark.django_db
class TestAPI:
def setup(self):
self.client = APIClient()
def test_create_object(self, object_data, object_name):
post_response = self.client.post("/api/object/", data=object_data, format="json")
assert status.is_success(post_response.status_code)
# make sure that report was created
get_response = self.client.get(f"/api/object/{object_name}/")
assert status.is_success(get_response.status_code)
# object exists here (test passes)
def test_get_object(self, object_name):
get_response = self.client.get(f"/api/object/{object_name}/")
assert status.is_success(get_response.status_code)
# object does not exists here (test failes)
You probably don't want to persist data between test cases. You should strive to have test cases that can run in an arbitrary order. If you want data to be shared between test cases, either initialize it in a fixture and reuse that across test cases or combine your test cases into one to ensure everything in that one test case runs sequentially.
Yes, pytest-django does clear your DB after each test run.
In order to solve this, add a pytest configuration file named pytest.ini on your root directory and add as following
[pytest]
addopts = --reuse-db
As this config file suggests, it reuses the current DB without creating a new DB.
So if you need to create a DB for your test, you will have to specify it on command by pytest --create-db
Your test case structure seems wrong. Test cases should never depend on each other. There can be resources such as Model objects common among test cases (for eg. by initialising them in setUpClass) but a resource created in one test case should not be usable in another test case
Instead of the way you've set your test cases, you could do it as:
To test your POST endpoint:
1.1. Make the POST request, as you have in test_create_object and assert the response info
1.2. Assert that the object actually exists, preferably by checking if it exists in the DB else by making a GET request
To test your GET endpoint:
2.1. Create the object preferably directly in the DB or by making a POST request
2.2. Make a GET request, as you have in test_get_object and assert the response info

django.contrib.auth.models.User.DoesNotExist: User matching query does not exist

I have error like in title when I'm trying to run test, I dont know whats going on but my testUser doesn't work properly, It's funny because i have identical test user in another project and there everything is ok.
test_api.py
class TaskDetailViewAPI(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username='test', password='test123')
self.user.save()
#classmethod
def setUpTestData(cls):
user = User.objects.get(id=1)
Task.objects.create(name='TestTask', user=user, status='NEW', date=date(2019, 4, 9), description='This is test')
def test_access_to_view_logged(self):
task= Task.objects.get(id=1)
login = self.client.login(username='test', password='test123')
self.assertTrue(login)
And this is test from another project where everything works fine
class CreateCommentAPI(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username='test', password='test123')
self.user.save()
#classmethod
def setUpTestData(cls):
Category.objects.create(name='PC', slug='pc')
Product.objects.create(
category=Category.objects.get(id=1),
name='Laptop', slug='laptop',
description='here is description',
photo=SimpleUploadedFile("file.jpeg", b"file_content", content_type="image/jpeg"),
price=1999, available='available'
)
def test_access_to_view_logged(self):
product = Product.objects.get(id=1)
login = self.client.login(username='test', password='test123')
response = self.client.get(reverse('add_comments', kwargs={'id': product.id}))
self.assertTrue(login)
self.assertEqual(response.status_code, 200, f'expected Response code 200, instead get {response.status_code}')
setUpTestData is called only once for the whole test class, but more importantly it is called before setUp.
Your working code doesn't have anything inside setUpTestData that depends on data in setUp, which is correct. But your non-working code does; it tries to access the User, which hasn't been created yet. You need to refactor things so that the User is either created inside setUpTestData, or the Task is created inside setUp.
Your test user's id might not be 1, instead of using the id, you could use the username in your setUpTestData method:
user = User.objects.get(username='test')
TL;DR: In my case, I forgot to create and apply the migrations:
python manage.py makemigrations
python manage.py migrate
Longer, I have modified Django models, but did not push/migrate the changes into the database, which actually holds the data about those models. So when my code asked for the model data, they were not in the DB, thus the matching query does not exist.
This is not the case for the OPs question, although my answer could be a solution to the error mentioned in the title, which got me here in the first place.

Python / Django - How to get all methods in a unittest class to share the same database?

This is my code:
Link to my imports are here:
https://github.com/django/django/blob/master/django/core/urlresolvers.py
https://github.com/django/django/blob/master/django/contrib/auth/models.py
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/status.py
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/test.py
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
class UserTests(APITestCase):
def test_create_user(self):
"""
Ensure we can create a new user object.
"""
url = reverse('user-list')
data = {'username': 'a', 'password': 'a', 'email': 'a#hotmail.com'}
# Post the data to the URL to create the object
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# Check the database to see if the object is created.
# This check works.
self.assertEqual(User.objects.count(), 1)
def test_get_user(self):
"""
Ensure we can get a list of user objects.
"""
# This fails and returns an error
self.assertEqual(User.objects.count(), 1)
When I run the test, it raises an error saying AssertionError: 0 != 1 because in the function test_get_user, the user created in test_create_user is not visible. Is there a way for me to get all the methods in a class to share the same database so that if I create a user in test_create_user, I can access it in the methods which come below it?
Edit: The reason I want them to share the same database for all the methods is because all of my test cases in the UserTests class will need a user created so I don't want to have to repeat the same code all the time even when it is tested in test_create_user.
I know I can use def setUp(self) but I'm doing a "create user" test in my first method so I want to be able to test if I can create it first before creating it in def setUp(self).
You should set up your data explicitly in each test. Tests must not depend on each other.

Categories

Resources