I want to test the APIs of a Django app. For example I have these APIs:
/user/register/
/user/login/
I wrote test.py like this:
class UserTest(APITestCase):
def test_register(self):
# ...
# self.assertEqual( ... )
def test_login(self):
# ...
# login with registered user (test_register function)
The test_register works properly but test_login does not work.
The user that I created in test_register not exists in database when test_login running.
How can I keep the state of database in test process?
And how can I set the running order for test class?
Unittests should be independent from each other, and therefore order should not matter.
If you need an action to be performed before every test, like creating a brand new user, you should put it in the setUp function
Then if you need your user to be registered for the login test, simply do it manually within the test. Basically:
test_register is to TEST the register workflow
test_login is to TEST the login workflow, and you can manually set the registered status there (you're not testing it here)
class UserTest(APITestCase):
def setUp(self):
# call super().setUp() if necessary
self.user = User.objects.create_user(**your_data)
# More stuff
def test_register(self):
# ...
# self.assertEqual( ... )
def test_login(self):
self.user.register() # or any action required
# ...
# login with registered user (test_register function)
Edit:
Just FYI, there are 4 very useful hooks in test cases:
setUpClass: Happens before the FIRST test
setUp: Happens before EVERY test
tearDown: Happens after EVERY test
tearDownClass: Happens after the LAST test
You'll often use the setUp function to prepare your DB and what not (as it gets reset after every test automatically)
Related
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
I'm working on a Django app that exposes an API with the help of Django REST Framework (DRF). I've gotten to the point where I need to create a testing framework and have been poring over both the DRF and Django testing docs.
I've defined a BaseTestCase class that sets up basic required data needed for all other test cases, as well as a ModelTestCase class that inherits from BaseTestCase, in order to make use of the setup performed. Here's how those look right now:
BaseTestCase
class BaseTestCase(APITestCase):
'''
This class does basic data setup required to test API endpoints
Creates 1+ users, sets the client to use that user for auth
'''
#classmethod
def create_data(cls):
'''
Create the users needed by automated tests
'''
# creates some data used by child test cases
#classmethod
def setUpClass(cls):
client = APIClient()
cls.client = client
# call the method to create necessary base data
cls.create_data()
# get a user and set the client to use their auth
user = get_user_model().objects.get(email='auto-test#test.com')
client.force_authenticate(user=user)
# cls.client = client
super(BaseTestCase, cls).setUpClass()
def test_base_data(self):
'''
This test ensures base data has been created
'''
# tests basic data to ensure it's created properly
def test_get_users(self):
'''
This test attempts to get the list of users via the API
It depends on the class setup being complete and correct
'''
url = '/api/users/'
response = BaseTestCase.client.get(url, format='json')
print(json.loads(response.content))
self.assertEqual(response.status_code, status.HTTP_200_OK)
ModelTestCase
class ModelTestCase(BaseTestCase):
#classmethod
def setUpClass(cls):
super(ModelTestCase, cls).setUpClass()
client = APIClient()
user = get_user_model().objects.all()[0]
client.force_authenticate(user=user)
cls.client = client
def test_create_model(self):
'''
Make sure we can create a new model with the API
'''
url = '/api/model/'
# set the request payload
response = ModelTestCase.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
When I run all tests, I get a failure because one of the BaseTestCase data verification asserts (based on a count of how many objects are present) fails due to there being too many (since the BaseTestCase has been set up twice- once on it's own, once as part of the setUpClass of ModelTestCase
When I run only the ModelTestCase, I get the following error:
======================================================================
ERROR: test_get_users (app.tests.ModelTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/path/to/tests.py", line 125, in test_get_users
response = BaseTestCase.client.get(url, format='json')
AttributeError: type object 'BaseTestCase' has no attribute 'client'
----------------------------------------------------------------------
Which I don't understand- shouldn't the setUpClass() of BaseTestCase be running as normal?
I've also tried defining the ModelTestCase to inherit from APITestCase- with this configuration, running all tests succeeds, but (I believe) only because tests are run alphabetically, so the BaseTestCase runs, sets up data, and then the ModelTestCase can make use of that data.
I'd like for the tests themselves to be independent- and I believe setUpData() can be used for that. However, I also want the client setup (for auth) as well as data setup (which will end up being relatively expensive, I think) to be shared across test cases so that it wouldn't need to be repeated per-case, which is why I thought creating a base class to inherit from was the way to go.
Is there a way to accomplish what I've outlined? Should I be using setUpData() instead of setUpClass()? Or is there a way to create my BaseTestCase class and have it not run when tests are executed?
According to Django Rest Framework Testing section, you should keep the default schema using setUp() methods. That's how I've found a resonable solution:
from django.urls import reverse
from rest_framework.test import APITestCase
import pytest
from core.factories import UserFactory, ClientFactory, AdministratorFactory, FacilitatorFactory, ParticipantFactory
class BaseAPITestCase(APITestCase):
def setUp(self):
self.user = UserFactory()
self.client.force_authenticate(user=self.user)
class UserTestCase(BaseAPITestCase):
def setUp(self):
# Call Parent's constructor.
super(self.__class__, self).setUp()
self.clients = ClientFactory(user=self.user)
self.administrator = AdministratorFactory(user=self.user)
self.facilitator = FacilitatorFactory(user=self.user)
self.participant = ParticipantFactory(user=self.user)
def test_get_users(self):
url = reverse("api_v1:user-list")
response = self.client.get(url)
self.assertEquals(response.data["count"], 1)
self.assertEquals(response.data["results"][0]["username"], self.user.username)
self.assertEquals(response.status_code, 200)
Result:
============================================================================== test session starts ===============================================================================
platform linux -- Python 3.7.6, pytest-5.3.0, py-1.8.1, pluggy-0.13.1 --
cachedir: .pytest_cache
Django settings: tests.settings (from ini file)
Using --randomly-seed=1589794116
plugins: django-3.7.0, django-test-migrations-0.1.0, randomly-3.1.0, hypothesis-4.53.0, timeout-1.3.0, testmon-1.0.0, cov-2.8.0, deadfixtures-2.1.0
collected 1 items
tests/api/test_basics.py::UserTestCase::test_get_users PASSED
I'm using Selenium along with StaticLiveServerTestCase to test a Django app.
The test is as follow:
class e2eTest(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
super(IntegrationTest, cls).setUpClass()
cls.data = load_data()
cls.driver = webdriver.PhantomJS("path_to_phantomjs")
cls.common = common(cls.driver, cls.live_server_url + settings.STATIC_URL + 'index.html')
def setUp(self):
for data in self.data:
data.refresh_from_db()
def test_login_1(self):
self.common.login('admin')
def test_login_2(self):
self.common.login('admin')
load_data() is used to populate test database (using apps models).
If I run:
1- python manage.py test login.tests: the tests start with test_login_1. test_login_1 succeed and test_login_2 fail.
2- python manage.py test login.tests --reverse: the tests start with test_login_2. test_login_2 succeed and test_login_1 fail.
I believe it have something to do with data begin erased after each test.
P.S. When using same approach using django.test.TestCase & setUpTestData and Django's test client it works ( sending the data to the login api directly ).
Can I have something like setUpTestData from django.test.TestCase ?
EDIT 1
#luke_aus: load_data() contain methods to populate the test database:
from my_app import User, priceModel
user = User(username='test').set_password('test')
user.save()
priceModel = priceModel(name='test')
priceModel.save()
....
#knbk: I'm using Django 1.9. The test still fail after passing --parallel=1.
Thanks you both for your feedback!
The problem was caused by load_data(). The database will get populated in the first call to load_data() from setUpClass, and the first test will passes. For the second test, the database data will be cleared and not refreshed.
Changing load_data() method to load fixtures solved the problem.
I have the application in Django REST as backend and Angular as frontend.
Suppose in This is my code
class ModelClass (models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def save(self, *args, **kwargs):
#check if the row with this hash already exists.
if not self.pk:
self.hash = self.create_hash()
self.my_stuff = 'something I want to save in that field'
# call to some async task
super(ModelClass, self).save(*args, **kwargs)
In my REST i have this view
class ModelListCreateView(generics.ListCreateAPIView):
model = ModelClass
serializer_class = ModelClassSerializer
def pre_save(self, obj):
obj.created_by = obj.updated_by = self.request.user.staff
def post_save(self, obj, created=False):
# add some other child objects of other model
I don't want to do unit testing. I want to do system testing so that I need to know if I post something to that view then
Pre-save thing should work
Record gets created
Save method of Model gets called with his stuff
After save method in REST gets called
Then I can assert all that stuff.
Can I test all that . I want to know which thing I need to have that sort of test rather than small unit tests
I am confused do I need to use Django test or REST Test or selenium test or PyTEst or factory boy because i want to know if things are actually getting in database
What you are looking is some kind of a REST Client code that would then be able to run your tests and you would be able to verify if the call is successful or not. Django Rest Framework has the APIRestFactory helper class that will aid you in writing such tests. The documentation can be found here and specifically look at the Example section. This would be part of your Django tests
Experiencing some fairly strange behaviour with django unit tests. I have a class inherited from django.test.testcase that verifies a registration form creates a person object and a user object, which it does, but I can subsequently look for and find the objects in the Admin interface after the tests have finished. This is the test:
import unittest
import time
from django.test import TestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from django.contrib.auth.models import User
from apps.persons.models import Person
class NewVisitorTest(TestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
def tearDown(self):
self.browser.quit()
def test_can_sign_up_and_login(self):
# User goes to register url
self.browser.get('http://localhost:8000/register/')
# User can see that she has come to the right page on the website
self.assertIn('Register', self.browser.title)
# User can see that there is a sign up form
signup_form = self.browser.find_element_by_id('id_registration_form')
# User can enter text into the first name field
first_name_input = self.browser.find_element_by_id('id_first_name')
first_name_input.send_keys('Jim')
# User can enter text into the last name field
last_name_input = self.browser.find_element_by_id('id_last_name')
last_name_input.send_keys('bob')
# User can enter text into the username field
username_input = self.browser.find_element_by_id('id_username')
username_input.send_keys('jim_bob')
# User can enter text into the email field
email_input = self.browser.find_element_by_id('id_email')
email_input.send_keys('jim_bob#jimbob.com')
# User can enter text into the first password field
password_input = self.browser.find_element_by_id('id_password')
password_input.send_keys('kittensarecute')
# User can enter text into the second password field
password_1_input = self.browser.find_element_by_id('id_password1')
password_1_input.send_keys('kittensarecute')
# Submit form
signup_form.submit()
time.sleep(20)
persons = Person.objects.all()
print persons
self.assertEqual(len(persons), 1)
users = User.objects.all()
print users
self.assertEqual(len(users), 1)
# Deliberately fail
self.fail('Finish the test!')
if __name__ == '__main__':
unittest.main()
The code fails on assertEqual(len(persons), 1). I can reproduce the same behaviour in the browser and have no issues. Each time I run the test I have to delete the user it creates in the main local database, so I'm assuming it's saving the objects in the database defined in settings['databases'], but then looking for them in the test database later in the method. I see in the terminal that the test database is created at the beginning of each test, so it's definitely there, which seems confusing. Any help would be massively appreciated.
EDIT: It's all on sqlite
File ".../tests/tests.py", line 60, in test_can_sign_up_and_login
self.assertEqual(len(persons), 1)
AssertionError: 0 != 1
Edit 2:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '/Users/me/github/stage/project/data/db.db', # Or path to database file if using sqlite3.
# The following settings are not used with sqlite3:
'USER': '',
'PASSWORD': '',
'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
'PORT': '', # Set to empty string for default.
}
}
I have a folder with apps in them, so to run these tests and run:
python manage.py test tests
You're right, django creates a test database inside your tests. From the docs:
Tests that require a database (namely, model tests) will not use your
“real” (production) database. Separate, blank databases are created
for the tests.
full documentation
Which is the correct thing to do. Unit test should be isolated from one another in order to avoid a dirty environment (a previous test forgets to remove a record/file/object it created and it affects the result of the other tests).
What you are doing is called Functional Testing. You are testing that your web application form submits, validates, saves, etc all in one test. Since the act of doing this implicates that many pieces of code run, it's not considered unit testing. When you perform these types of tests, your assertions should also be similar:
assert thank you/welcome message is shown
go to the user management page and assert new person is there
check that they can log it
etc
This tests that your application functions correctly.
The difference between the two types of testing is that Functional Testing tests what your application is doing, whereas Unit Testing tests how it's doing it.
This is crucial because you could be saving your data in a number of ways:
As JSON into a file
As XML
In a database locally
In the cloud somewhere
Your functional test doesn't really care how this is done, it just cares that when you go to a specific page, it gets the data. But unit test will drastically change depending on which way you're using.
In your case, you are using both ways of testing. Selenium (opening a page, filling it out, submitting it) is testing what the application does (functional), but your assertions (was the data was saved to the database) is testing how you store the data (unit).
So I suggest that you change your assertions to fit the rest of your test. And move the current ones into your unit tests:
def test_user_is_save(self):
addUser(...)
users = User.objects.all()
self.assertEqual(len(users), 1)
This will test that your addUser method is working correctly (saving to the db), which is what unit test is about. And your current test will test that when you submit the form, the user sees the right thing.
You can use LiveServerTestCase instead and access to your service with
self.live_server_url.
You will be able to get objects from the test database models normally with Entry.objects.get
import requests
from django.test import LiveServerTestCase
class FunctionalTest(LiveServerTestCase):
def test_get_comments(self):
response = requests.get(f"{self.live_server_url}/api/comments/?page=1")
self.assertEqual(response.status_code, 200)
You should use LiveServerTestCase, so that django can run it's own test server and therefore use it's own test db.
https://docs.djangoproject.com/en/1.4/topics/testing/#live-test-server