2

I have a simple model as,

class Person(models.Model):
    first_name = models.CharField(max_length=20)

and I have setup the GraphQL to query the data,

import graphene
import graphene_django
from .models import Person


class PersonType(graphene_django.DjangoObjectType):
    class Meta:
        model = Person
        fields = '__all__'


class PersonQuery(graphene.ObjectType):
    persons = graphene.List(PersonType)

    def resolve_persons(*args, **kwargs):
        return Person.objects.all()

So far so good. Later I decided to write unittests for querying the persons data

from django.test import TestCase
from .models import Person
from .schema import schema


class TestGraphQLQuery(TestCase):

    @classmethod
    def setUpTestData(cls):
        cls.person = Person.objects.create(first_name="Jack")

    def test_person_query(self):
        query = """
            query{
              persons {
                id
                firstName
              }
            }
        """
        result = schema.execute(query).data
        expected = {'persons': [{'id': f'{self.person.pk}', 'firstName': self.person.first_name}]}
        self.assertEqual(result, expected)

and this too working.

Later, my model got updated with one additional field, age,

class Person(models.Model):
    first_name = models.CharField(max_length=20)
    age = models.IntegerField(default=0)

After the changes, I ran the unittests. As expected, it passes.

Question

How can I create the test case so that, the test should fail upon addition or removal of any fields?

Advantages of this test cases that I am seeking

  1. We will get notified whenever a new field added to the model
  2. We will get notified whenever a field removed or renamed
  3. Generating dynamic graphql query will also help to verify the returned data from the schema.
JPG
  • 82,442
  • 19
  • 127
  • 206

2 Answers2

0

If monitoring Django model changes is required, Django already has such a feature: https://docs.djangoproject.com/en/3.1/ref/django-admin/#cmdoption-makemigrations-check

Whenever verified (already checked) model changes are made they should be persisted as a migration script with makemigrations command. These migration scripts are then used as a reference point by django-admin makemigrations --check to check for model mutations (i.e. added / changed / removed fields).

Attila Viniczai
  • 644
  • 2
  • 8
  • What if I don't want that notification "for ***all*** models", but ***"only"*** for certain models? – JPG Sep 27 '20 at 15:37
  • That is a bit trickier. One solution could be to organize all models that need to be monitored in a separate Python module. Then create migration scripts, and run `django-admin makemigrations --check` separately on these modules through `INSTALLED_APPS` setting: https://docs.djangoproject.com/en/3.1/ref/settings/#std:setting-INSTALLED_APPS (ref: https://docs.djangoproject.com/en/3.1/topics/db/models/#using-models ) In short: create a separate Django "app" for the models that need to be monitored. – Attila Viniczai Sep 27 '20 at 15:45
  • I don't think it is a viable solution coz, we usually create and organize the models as per our business logic. – JPG Sep 27 '20 at 16:07
  • Another solution could be to control which models are monitored with a dynamically set `Meta.managed` attribute on the models: https://docs.djangoproject.com/en/3.1/ref/models/options/#django.db.models.Options.managed This `managed` attribute then could be toggled on models that need to be ignored using an environment variable: `managed = os.environ.get('DJANGO_MODEL_MONITORING') is None` – Attila Viniczai Sep 27 '20 at 16:46
  • Also `MIGRATION_MODULES` will need to be set to a different directory. ref: https://stackoverflow.com/questions/46486260/is-it-possible-to-change-the-migrations-folders-location-outside-of-the-djang And `makemigrations` has to be run with `--dry-run` flag: https://docs.djangoproject.com/en/3.1/ref/django-admin/#cmdoption-makemigrations-dry-run – Attila Viniczai Sep 27 '20 at 16:57
0

We can use the introspection query to get the fields of a specific type.

from django.test import TestCase
from .models import Person
from .schema import schema


class TestGraphQLQuery(TestCase):

    @classmethod
    def setUpTestData(cls):
        cls.person = Person.objects.create(first_name="Jack")

    def test_person_query(self):
        introspection_query = """
            {
               __type(name:"PersonType") {
                  fields {
                     name
                  }  
               }
            }
        """
        introspection_result = schema.execute(introspection_query).data["__type"]["fields"]
        introspection_expected = [{'name': 'id'}, {'name': 'firstName'}, {'name': 'age'}]
        self.assertCountEqual(introspection_result, introspection_expected)

        dynamic_person_fields = "\n".join([item['name'] for item in introspection_result])
        data_query = f"""
            query{{
              persons {{
                {dynamic_person_fields}
              }}
            }}
        """
        data_result = schema.execute(data_query).data["persons"]
        data_expected = [{'id': f'{self.person.pk}', 'firstName': self.person.first_name, 'age': self.person.age}]
        self.assertEqual(data_result, data_expected)

Reference

JPG
  • 82,442
  • 19
  • 127
  • 206