Package: django-contract-tester
Django Contract TesterThis is a test utility to validate DRF (Django REST Framework) test requests & responses against OpenAPI 2 and 3 schema.It has built-in support for:OpenAPI 2/3 yaml or json schema files.OpenAPI 2 schemas created withdrf-yasg.*OpenAPI 3 schemas created withdrf-spectacular.**Fordrf-yasganddrf-spectacularsupport is for now only for responses.Installationpipinstalldjango-contract-testerUsageInstantiate one or more instances ofSchemaTester:fromopenapi_testerimportSchemaTesterschema_tester=SchemaTester()If you are using eitherdrf-yasgordrf-spectacularthis will be auto-detected, and the schema will be
loaded by theSchemaTesterautomatically.If you are using schema files, you will need to pass the file path:fromopenapi_testerimportSchemaTester# path should be a stringschema_tester=SchemaTester(schema_file_path="./schemas/publishedSpecs.yaml")Once you've instantiated a tester, you can use it to test responses and request bodies:fromopenapi_tester.schema_testerimportSchemaTesterschema_tester=SchemaTester()deftest_response_documentation(client):response=client.get('api/v1/test/1')assertresponse.status_code==200schema_tester.validate_response(response=response)deftest_request_documentation(client):response=client.get('api/v1/test/1')assertresponse.status_code==200schema_tester.validate_request(response=response)If you are using the Django testing framework, you can create a baseAPITestCasethat incorporates schema validation:fromrest_framework.responseimportResponsefromrest_framework.testimportAPITestCasefromopenapi_tester.schema_testerimportSchemaTesterschema_tester=SchemaTester()classBaseAPITestCase(APITestCase):""" Base test class for api views including schema validation """@staticmethoddefassertResponse(response:Response,**kwargs)->None:""" helper to run validate_response and pass kwargs to it """schema_tester.validate_response(response=response,**kwargs)Then use it in a test file:fromshared.testingimportBaseAPITestCaseclassMyAPITests(BaseAPITestCase):deftest_some_view(self):response=self.client.get("...")self.assertResponse(response)OptionsYou can pass options either globally, when instantiating aSchemaTester, or locally, when
invokingvalidate_response:fromopenapi_testerimportSchemaTester,is_camel_casefromtests.utilsimportmy_uuid_4_validatorschema_test_with_case_validation=SchemaTester(case_tester=is_camel_case,ignore_case=["IP"],validators=[my_uuid_4_validator])Orfromopenapi_testerimportSchemaTester,is_camel_casefromtests.utilsimportmy_uuid_4_validatorschema_tester=SchemaTester()defmy_test(client):response=client.get('api/v1/test/1')assertresponse.status_code==200schema_tester.validate_response(response=response,case_tester=is_camel_case,ignore_case=["IP"],validators=[my_uuid_4_validator])case_testerThe case tester argument takes a callable that is used to validate the key case of both schemas and responses. If
nothing is passed, case validation is skipped.The library currently has 4 built-in case testers:is_pascal_caseis_snake_caseis_camel_caseis_kebab_caseYou can use one of these, or your own.ignore_caseList of keys to ignore when testing key case. This setting only applies when case_tester is notNone.validatorsList of custom validators. A validator is a function that receives two parameters: schema_section and data, and returns
either an error message orNone, e.g.:fromtypingimportAny,OptionalfromuuidimportUUIDdefmy_uuid_4_validator(schema_section:dict,data:Any)->Optional[str]:schema_format=schema_section.get("format")ifschema_format=="uuid4":try:result=UUID(data,version=4)ifnotstr(result)==str(data):returnf"Expected uuid4, but received{data}"exceptValueError:returnf"Expected uuid4, but received{data}"returnNonefield_key_mapYou can pass an optional dictionary that maps custom url parameter names into values, for situations where this cannot be
inferred by the DRFEndpointEnumerator. A concrete use case for this option is when
thedjango i18n locale prefixes.fromopenapi_testerimportSchemaTesterschema_tester=SchemaTester(field_key_map={"language":"en",})Schema ValidationWhen the SchemaTester loads a schema, it parses it using anOpenAPI spec validator. This validates the schema.
In case of issues with the schema itself, the validator will raise the appropriate error.Django testing clientThe library includes anOpenAPIClient, which extends Django REST framework'sAPIClientclass.
If you wish to validate each request and response against OpenAPI schema when writing
unit tests -OpenAPIClientis what you need!To useOpenAPIClientsimply passSchemaTesterinstance that should be used
to validate requests and responses and then use it like regular Django testing client:schema_tester=SchemaTester()client=OpenAPIClient(schema_tester=schema_tester)response=client.get('/api/v1/tests/123/')To force all developers working on the project to useOpenAPIClientsimply
override theclientfixture (when usingpytestwithpytest-django):frompytest_django.lazy_djangoimportskip_if_no_djangofromopenapi_tester.schema_testerimportSchemaTester@pytest.fixturedefschema_tester():returnSchemaTester()@pytest.fixturedefclient(schema_tester):skip_if_no_django()fromopenapi_tester.clientsimportOpenAPIClientreturnOpenAPIClient(schema_tester=schema_tester)If you are using plain Django test framework, we suggest to create custom
test case implementation and use it instead of original Django one:importfunctoolsfromdjango.test.testcasesimportSimpleTestCasefromopenapi_tester.clientsimportOpenAPIClientfromopenapi_tester.schema_testerimportSchemaTesterschema_tester=SchemaTester()classMySimpleTestCase(SimpleTestCase):client_class=OpenAPIClient# or use `functools.partial` when you want to provide custom# ``SchemaTester`` instance:# client_class = functools.partial(OpenAPIClient, schema_tester=schema_tester)This will ensure you all newly implemented views will be validated against
the OpenAPI schema.Known IssuesWe are usingpranceas a schema resolver, and it has some issues with the
resolution of (very) complex OpenAPI 2.0 schemas.ContributingContributions are welcome. Please see thecontributing guide