rest-framework-auto-tester

An Automatic Endpoint tester for django rest framework


Install
pip install rest-framework-auto-tester==0.1.1

Documentation

Django Automatic Tester

What the hell does this thing do

Well, automatically tests your rest framework. This library was written with lazyness in mind. I recently wrote too many microservices and faaar to many of the same tests for them.

This library is intended to make the testing phase super simple and fast for rest framework stuff. The golden rule here is only test things that aren't tested. So you really only want to write tests for the things your write outside the scope of the rest framework. Ideally, your own business logic. Everything else is pretty well tested both in django and django rest framework.

Requires

  • python: 3.7
  • djangorestframework: 3.9
  • django: 2.1

How to use it

Assuming you're writing a bunch of endpoints using rest-framework you should be following this kind of structure with ModelSerializer and ModelViewSet.

some_project/
    urls.py
    settings.py
    wsgi.py
some_app/
    models.py
    serializers.py
    tests.py
    viewsets.py
    models.py

To expand just a little:

models.py
---------
class SomeModel(models.Model):
    ...

serializers.py
--------------
class SomeSerializer(serializers.Serialzer):
    class Meta:
        model = SomeModel
        exclude = ()
    ...


viewsets.py
--------------
class SomeViewSet(viewsets.ModelViewSet):
    serializer_class = SomeSerialzer
    queryset = SomeModel.objects.all()
    ...

urls.py
-------
router.register('some-endpoint', SomeViewSet, 'some_url_name')

...

    path('api/', include((router.urls, 'api'), namespace='my_api')),

Okay so using this approach we can easily automate our testing quite a bit.

tests.py
--------
from rest_framework_autotester import AutoTester


class SomeEndPointTestCase(AutoTester):
    model = SomeModel
    viewset = SomeViewSet
    serializer = SomeSerialzer
    url_namespace = "my_api"
    url_name = "some_url_name"

With the above setup you already have six tests written for you, one for create, update, partial update, list, retrieve and destroy. This will check the responses received and the status code and make sure that its all up to scratch.

Extending the tests to test business logic.

Assuming you have specific input and output test cases, specific validation test cases, you can easily these with minimal code that you need to write.

Assuming we want to test serializer validation with some custom business logic, like this:

serializers.py
--------------

class SomeSerializer(serializers.Serializer):
    send_welcome_email = serializers.BooleanField()
    user_is_active = models.BooleanField()

    def validate(self, data):
        data = super(SomeSerializer, self).validate(data)

        # Make sure that send_welcome_email is not true when user_is_active is false

        if data['send_welcome_email'] and not data['user_is_active']:
            raise serializers.ValidationError(
                {
                    'send_welcome_email': [
                        'This field cannot be true if user_is_active is false'
                    ]
                }
            )

We can easily do so:

tests.py
--------
from rest_framework_autotester import AutoTester


class SomeEndPointTestCase(AutoTester):

    model = SomeModel
    viewset = SomeViewSet
    serializer = SomeSerialzer
    url_namespace = "my_api"
    url_name = "some_url_name"

    def test_cannot_send_welcome_email_if_user_not_active_create(self):

        # Create a test payload with all the other required fields
        payload = self.create_payload()

        # Update fields that we want to test against
        payload['send_welcome_email] = True
        payload['user_is_active] = False

        response = self.handle_request(
            'create', payload=payload
        )

        self.assertEqual(response.status_code, 400)
        ...
        # You can add more assertions if you want

Handling related data

Lets say that you have a model setup like this.

class ModelA(models.Model):
    ...

class SomeModel(models.Model):
    a_related_field = models.ForeignKey(ModelA):
    ...

You can handle this via the the automatic test framework in two ways

1. Let the framework handle it for you

class SomeEndPointTestCase(AutoTester):

    model = SomeModel
    viewset = SomeViewSet
    serializer = SomeSerialzer
    url_namespace = "my_api"
    url_name = "some_url_name"
    related_fields = {
        'a_related_field': {
            'serializer': ModelASerialzer  # Seraizer that will auto gen a payload
            OR
            'data': {}  # Your own data to create the payload

        }
    }

Depending on what you provide, a related object will be created and used and applied to the test_payload.

2. Create your own test objects

If you are hell bent on writing your own code to do this then you will just need to overwrite a method to do so.

def create_related_data(self):
    """
    Returns a dict of k, v where k is the key of the related data
    """
    related_data = {
        'a_related_field': ModelA.objects.create(
        ...
    )

Extending setUp()

def setUp(self):
    # ... Do what you need to do
    ...

    # Let the auto tester do its thing.
    super(SomeTestCase, self).setUp()

    # .. Do what you need to do again
    ...