I have just moved all of my AJAX validation code over to Django Forms. I need some help updating my tests. I basically have some test data, declared as constants, that are used across all suites. I am then this data repeatedly throughout my tests.
As part of the setup I create some users and login the user that I need:
def setUp(self):
self.client = Client()
create_user(username='staff', email='staff#staff.com',
password='staff', staff=True)
create_user(username='agent', email='agent#agent.com',
password='agent', staff=False)
ShiftType.objects.create(type_id='SI', description='Sales Inbox')
self.client.login(username='staff', password='staff')
The tear down deletes this data (or it used to):
def tearDown(self):
# Clean up the DB
self.client.logout()
ShiftType.objects.all().delete()
User.objects.all().delete()
Event.objects.all().delete()
RecurrentEvent.objects.all().delete()
This was working fine, but now the form does not validate because the form value id given by the users is incremented each time. For example:
ERROR: <ul class="errorlist"><li>employee_id<ul class="errorlist"><li>Select a valid choice. That choice is not one of the available choices.</li></ul></li></ul>
Printing the form allows me to see that the ids are being incremented each time.
Why is this happening even though I am deleting all employees?
I would look into using text fixtures rather than creating and deleting the data every time. This is pretty easy to do in Django. In your tests.py it would look something like this
class BlogTest(TestCase):
fixtures = ['core/fixtures/test.json']
When you do this django will build you a test database, load the fixture into it, run your tests, and then blow away the database after the tests are done. If you want to even use a different database engine (we do this to have our tests use sqlite because it is fast) you can throw something like this into your settings.py
This will make it so the ID's are the same every single time which would fix your problem.
Related
I want to use my test database with data in it which was created when testcase ran, how can i do it?
I tried running normal django test which inherits TestCase and put a break point after test data is been generated. Now if I login to the test_db (which django creates) in different terminal tab through postgres command and query it, no data is shown! can someone explain why this happens?
TestCase wraps tests in an atomic block and rolls back the transaction so that no changes are saved to the database.
If you want changes to be saved, you could use SimpleTestCase, and set databases = __all__ (Django 2.2+), or allow_database_queries = True (earlier versions of Django).
The docs for the different test case classes are here
I am unsure of what situations I would use each of the test case classes:
APITestCase
APISimpleTestCase
APITransactionTestCase
As explained in the Django Rest Framework Docs, the 3 available test classes simply extend the regular Django test classes but switch the client to use APIClient.
This can also be seen in the Django Rest Framework source code
class APITransactionTestCase(testcases.TransactionTestCase):
client_class = APIClient
class APITestCase(testcases.TestCase):
client_class = APIClient
class APISimpleTestCase(testcases.SimpleTestCase):
client_class = APIClient
The first test case you should know about is the APISimpleTestCase which allows us to test general DRF/Django things such as http redirects and checking some callable raises an exception. The docs note that we shouldn't use APISimpleTestCase when doing any testing with the database.
The reason we shouldn't use APISimpleTestCase with the database is because the test data would stay in the database across multiple tests. To get around this we must use APITransactionTestCase which will use atomic() blocks to wrap tests in transactions and allow the test runner to roll back the database at the beginning of each test, allowing easy atomic testing of database related actions. It also adds some extra assertion methods related to database assertions such as assertNumQueries.
Finally, the APITestCase wraps the tests with 2 atomic() blocks, one for the whole test class and one for each test within the class. This essentially stops tests from altering the database for other tests as the transactions are rolled back at the end of each test. By having this second atomic() block around the whole test class, specific database transaction behaviour can be hard to test and hence you'd want to drop back to using APITransactionTestCase.
I'm working on a project based on Django REST Framework. So I need to write some test cases for my REST API.
I've written some basic class (let's call it BaseAPITestCase) inherited from standard DRFs APITransactionTestCase.
In this class I've defined setUp method where I'm creating some test user which belongs to some groups (I'm using UserFactory written with FactoryBoy).
When I run my tests, the first one (first test case method from first child class) successfully creates a user with specified groups, but the others don't (other test case methods in the same class).
User groups just don't exist in DB at this time. It seems like existed records are deleted from DB at each new test case run. But how then it works for the first time?
I've read Django test documentation but can't figure out why it happens... Can anyone explain it?
The main question is what I should do to make these tests works?
Should I create user once and store it in object variable?
Should I add some params to preserve user groups data?
Or should I add user groups to fixtures? In that case, how can I create this fixture properly? (All related models, such as permissions and content types)
Simplified source code for illustration:
from rest_framework.test import APITransactionTestCase
class BaseAPITestCase(APITransactionTestCase):
def setUp(self):
self.user = UserFactory(
username='login',
password='pass',
group_names=('admin', )
)
self.client = APIClient()
self.client.force_login(self.user)
def tearDown(self):
self.client.logout()
class CampaignListTest(BaseAPITestCase):
def test_authorized_get(self):
# successfully gets user groups from DB
def test_authorized_post(self):
# couldn't find any groups
TransactionTestCase is a test case to test transactions. As such, it explicitly does not use transactions to isolate tests, as that would interfere with the behaviour of the transactions that are being tested.
To isolate tests, TransactionTestCase rolls back the database by truncating all tables. This is the easiest and fastest solution without using transactions, but as you noticed this will delete all data, including the groups that were generated in a post_migrate signal receiver. You can set serialized_rollback = True on the class, in which case it will serialize all changes to the database, and reverse these changes them after each test. However, this is significantly slower, and often greatly increases the time it takes to run the test suite, so this is not the default.
TestCase does not have this restriction, so it wraps each test case in a transaction, and each individual test in a savepoint. Roll back using transactions and savepoints is fast and allows you to keep the data that was there at the start of the transaction or savepoint. For this reason, it is preferable to use TestCase whenever possible.
This extends to DRF's APITransactionTestCase and APITestCase, which simply inherit from Django's test cases.
I started working on a Django project recently. To start off with, I read the book on TDD with Python and the official documentation (for tests). And a few more blogs as well.
One thing I notice is, they write tests that access the database models. Consider the following snippet from here
def test_home_page_can_save_a_POST_request(self):
request = HttpRequest()
request.method = 'POST'
request.POST['item_text'] = 'A new list item'
response = home_page(request)
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new list item')
The test asserts whether the number of Item objects is 1. So the test actually adds and retrieves data from the database. Wouldn't that make the test slow?
If the tests are parallelized, this test case may fail if there is another test which adds an Item object, right?
How about patching methods / objects? The above snippet can be refactored like this
#patch('my_app.views.Item')
def test_home_page_can_save_a_POST_request(self, mock_item):
request = HttpRequest()
request.method = POST
request.POST['item_text'] = 'A new list item'
response = home_page(request)
self.assertTrue(mock_item.objects.create.called)
I am new to Django and I am not familiar with the practices. The tutorials I visited wrote tests that talk to database. I would like to know, whether that is the convention for testing in Django projects. Is the second snippet (using patches and mock) perfectly fine in the Django ecosystem?
Edit: Same with Forms as well - Mocking form.is_valid method to return True or False, provided there are separate unit tests for Forms.
P.S. TDD with Python is an awesome book that helped me to a great extend in on-boarding. I definitely recommend that to anyone learning Django.
If you want to test your models, you can hardly bypass the database, and yes indeed it can make the tests somewhat slower, even if using an in-memory SQLite db - which can lead to other problems FWIW since SQLite is not a transparant replacement for something like PostgreSQL.
But assuming whatever model or or other function / object / method you call in your views have their own unittest and you only want to check your views do the expected calls, mocking is viable strategy - actually probably saner than testing the results of the expected calls (which mostly turn your unittests into integration tests).
I'm using django rest framework and trying to save some data so it will be accessible by GET, PUT, DELETE.
So when user send GET request server send some information (a random number, for example) and that information is needed after user sends PUT request on the same url. How would one save such information? I'm using class-based views.
So i want to save that information on GET method.
I tried saving that information to class variable self.information, but the problem is self.information is empty when PUT method is getting called.
I also tried saving it to session, but like class variable, session is also empty when PUT method is being executed.
class SampleClass(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, generics.GenericAPIView):
serializer_class = SampleSerializer
def get(self, request):
random_number = random.randint(0, 10)
request.session['number'] = random_number;
content = {'random_number': random_number}
return Response(content)
def put(self, request):
number = request.session['number'] # key doesn't exists
process_number(number)
# ...
Before I begin, it's important to note that HTTP is a stateless protocol, and you are looking to add state into the mix. If you can rework what you are doing to not depend on previous requests, that will probably be better in the long run.
I tried saving that information to class variable self.information, but the problem is self.information is empty when PUT method is getting called.
This is because the class is re-initialized for each request. Because of that, the class variables don't persist across requests. Even if they did, that would mean everyone would get access to the persisted value, and it isn't made clear if that is what you are looking for.
I also tried saving it to session, but like class variable, session is also empty when PUT method is being executed
This doesn't work because Django sessions are persisted through the use of cookies. While this might work for SessionAuthentication, it won't work for any authentication that happens outside of the browser. This is because the session cookies won't be included, so Django will think the new requests are under a different session.
Now, just because HTTP is mostly stateless and doing this might lead to future trouble, that doesn't mean that you should never do it. The Django sessions wouldn't exist if there wasn't a need for it, and there are ways to save state without Django sessions.
Create a new model for the state - This is usually the best way to save state per-user and ensure that it doesn't fade away. The model needs a user field along with the fields that the state will be stored in, and all you need to do is have a query that retrieves the state object for the user.
Use the Django cache - This is the way I would recommend it for the case that you specified in your question. When you don't need to store much state, the state is shared among everyone, or you can live with it not existing (expiring), storing the data in a simple cache environment will probably work the best. You have much more control over what is stored, but at the expense of having to do more work.