Django rest framework: unit testing database issue

2019-09-06 06:14发布

问题:

I am doing unit testing of the rest Apis. I am using django rest framework. Apis are saving data into and getting data from the database. Both of the operations are not working or if it is working i am not able to see that in the databases. Apis are also using django-fsm, because of which i need same data from the db for the other tests. Tests depends on previous tests due to django-fsm. There is always state changing with the api. But now i am not able to see any data in database during test runs. Don't know where it is saving the data or in which database.

Below is my test settings:-

DATABASES = {
'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': join(PROJECT_ROOT, 'run', 'db_for_testing.sqlite3'),
    'TEST': {
        'NAME': 'test_db_for_testing',
    },
 },
}

below is my api:-

class DummyView(CreateAPIView):
    def post(self, request, *args, **kwargs):
        data = request.data.copy()
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        order = self.model(book=serializer.data.get('book'))
        order.save()
        data = {
        'status_code': 200,
        'message': 'successfully.'
        }
        return Response(data, status=status.HTTP_200_OK)

As my tests depends on the previous test saving the data to db, so the other tests also fails. I am using APITestCase of rest_framework. Help guys. thanks in advance.

回答1:

If I'm understanding your question correctly, Django "clear" database after each test (either rolling back or truncating.) So you need to write your tests accordingly.

See: https://docs.djangoproject.com/en/1.10/topics/testing/tools/#transactiontestcase



回答2:

TL;DR - Solution: Use SimpleTestCase - See example below


Explanation

The thing is that the recommended test classes provided by Django for tests involving database queries, TransactionTestCase and the subclass TestCase, wraps every test in a transaction to speed up the process of resetting the database after each test. Source: Django TransactionTestCase docs

It is possible to avoid this behaviour by using SimpleTestCase which is the parent class of TransactionTestCase. You then have to specify explicitly that you want to allow database queries by setting allow_database_queries to True-

Also note that you are then responsible for any cleaning that needs to be done after the test. You can do that by overriding the tearDownClass class method. Similarly there's a setUpClass class method for any initialization prior to running the test. Remember to call the super methods. See full details in the docs


Example

from django.test import SimpleTestCase

class MyTestCase(SimpleTestCase):
    allow_database_queries = True

    @classmethod
    def setUpClass(cls):
        # Do your pre test initialization here.
        super(MyTestCase, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        # Do your post test clean uphere.
        super(MyTestCase, cls).tearDownClass()

    def test_add_data_through_api(self):
        # Add 'dat data
        ...

    def test_work_with_data_from_previous_test(self):
        # Work 'dat data
        ...


回答3:

Use the --keepdb option when calling manage.py test:

https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-test-keepdb

It's available since django 1.8. Hope this helps.



回答4:

Here my three tests are dependent with previous one. If I run them as separate test the previous test data deleted and later one failed for lost of that data.

So I make them in two different functions both are not test function. Finally call the dependent functions from one test function.

class test_profile(APITestCase):
    def setUp(self):
        super_user = default_service.create_super_user()
        self.application = default_service.create_oath2_application(super_user.id)
        self.content_type = "application/x-www-form-urlencoded"
        self.username = "user@domain.com"
        self.password = "pass123"

    def create_profile(self):
        url = reverse('EmailSignUp')
        body = {
            "email": self.username,
            "password": self.password,
            "fullname": "Mamun Hasan"
        }
        response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = (json.loads(response.content))['data']
        # print("Profile", data)
        self.assertEqual(data['username'], self.username)
        self.assertEqual(data['fullname'], data['fullname'])

    def get_access_token(self):
        url = reverse('oauth2_provider:token')
        body = {
            "username": self.username,
            "password": self.password,
            "grant_type": self.application.authorization_grant_type,
            "client_id": self.application.client_id,
            "client_secret": self.application.client_secret,
        }
        response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = (json.loads(response.content))
        # print("AccessToken", data)
        self.assertEqual(data['token_type'], 'Bearer')
        return data

    def get_profile(self, oath2_token):
        url = reverse('GetProfile')
        authorization = oath2_token["token_type"] + ' ' + oath2_token["access_token"]
        response = self.client.get(url, CONTENT_TYPE=self.content_type, HTTP_AUTHORIZATION=authorization)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = (json.loads(response.content))['data']
        # print("Profile", data)
        self.assertEqual(data['username'], self.username)

    def test_dependent(self):
        self.create_profile()
        oath2_token = self.get_access_token()
        self.get_profile(oath2_token)

I did not find any solution to commit the previous API data. If anyone knows please comment. So I have done it this way. I don't know this is the best solution but it works and tested.