1

I'm writing an endpoint to receive and parse GitHub Webhook payloads using Django Rest Framework 3. In order to match the payload specification, I'm writing a payload request factory and testing that it's generating valid requests.

However, the problem comes when trying to test the request generated with DRF's Request class. Here's the smallest failing test I could come up with - the problem is that a request generated with DRF's APIRequestFactory seems to not be parsable by DRF's Request class. Is that expected behaviour?

from rest_framework.request import Request from rest_framework.parsers import JSONParser from rest_framework.test import APIRequestFactory, APITestCase class TestRoundtrip(APITestCase): def test_round_trip(self): """ A DRF Request can be loaded into a DRF Request object """ request_factory = APIRequestFactory() request = request_factory.post( '/', data={'hello': 'world'}, format='json', ) result = Request(request, parsers=(JSONParser,)) self.assertEqual(result.data['hello'], 'world') 

And the stack trace is:

E ====================================================================== ERROR: A DRF Request can be loaded into a DRF Request object ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/request.py", line 380, in __getattribute__ return getattr(self._request, attr) AttributeError: 'WSGIRequest' object has no attribute 'data' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/james/active/prlint/prlint/github/tests/test_payload_factories/test_roundtrip.py", line 22, in test_round_trip self.assertEqual(result.data['hello'], 'world') File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/request.py", line 382, in __getattribute__ six.reraise(info[0], info[1], info[2].tb_next) File "/home/james/active/prlint/venv/lib/python3.4/site-packages/django/utils/six.py", line 685, in reraise raise value.with_traceback(tb) File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/request.py", line 186, in data self._load_data_and_files() File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/request.py", line 246, in _load_data_and_files self._data, self._files = self._parse() File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/request.py", line 312, in _parse parsed = parser.parse(stream, media_type, self.parser_context) File "/home/james/active/prlint/venv/lib/python3.4/site-packages/rest_framework/parsers.py", line 64, in parse data = stream.read().decode(encoding) AttributeError: 'str' object has no attribute 'read' ---------------------------------------------------------------------- 

I'm obviously doing something stupid - I've messed around with encodings... realised that I needed to pass the parsers list to the Request to avoid the UnsupportedMediaType error, and now I'm stuck here.

Should I do something different? Maybe avoid using APIRequestFactory? Or test my built GitHub requests a different way?


More info

GitHub sends a request out to registered webhooks that has a X-GitHub-Event header and therefore in order to test my webhook DRF code I need to be able to emulate this header at test time.

My path to succeeding with this has been to build a custom Request and load a payload using a factory into it. This is my factory code:

def PayloadRequestFactory(): """ Build a Request, configure it to look like a webhook payload from GitHub. """ request_factory = APIRequestFactory() request = request_factory.post(url, data=PingPayloadFactory()) request.META['HTTP_X_GITHUB_EVENT'] = 'ping' return request 

The issue has arisen because I want to assert that PayloadRequestFactory is generating valid requests for various passed arguments - so I'm trying to parse them and assert their validity but DRF's Request class doesn't seem to be able to achieve this - hence my question with a failing test.

So really my question is - how should I test this PayloadRequestFactory is generating the kind of request that I need?

2 Answers 2

1

"Yo dawg, I heard you like Request, cos' you put a Request inside a Request" XD

I'd do it like this:

from rest_framework.test import APIClient client = APIClient() response = client.post('/', {'github': 'payload'}, format='json') self.assertEqual(response.data, {'github': 'payload'}) # ...or assert something was called, etc. 

Hope this helps

Sign up to request clarification or add additional context in comments.

3 Comments

OK so the reason that I'm building custom requests wasn't clear enough in my question - it's because GitHub sends a X-GitHub-Event header which I need to emulate in my test suite. Adding more info to the question...
@jamesc then add the header in the client client = APIClient(HTTP_X_GITHUB_EVENT='pull_request'). Not tested, it works with Django's Client class.. Did it once github.com/pleasedontbelong/slack_dispatcher/blob/master/events/…
Thanks that's super helpful - and helpful to see that I named HTTP_X_GITHUB_EVENT correctly.
1

Looking at the tests for APIRequestFactory in DRF, stub views are created and then run through that view - the output is inspected for expected results. Therefore a reasonable, but slightly long solution, is to copy this strategy to assert that the PayloadRequestFactory is building valid requests, before then pointing that at a full view.

The test above becomes:

from django.conf.urls import url from django.test import TestCase, override_settings from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.test import APIRequestFactory @api_view(['POST']) def view(request): """ Testing stub view to return Request's data and GitHub event header. """ return Response({ 'header_github_event': request.META.get('HTTP_X_GITHUB_EVENT', ''), 'request_data': request.data, }) urlpatterns = [ url(r'^view/$', view), ] @override_settings(ROOT_URLCONF='github.tests.test_payload_factories.test_roundtrip') class TestRoundtrip(TestCase): def test_round_trip(self): """ A DRF Request can be loaded via stub view """ request_factory = APIRequestFactory() request = request_factory.post( '/view/', data={'hello': 'world'}, format='json', ) result = view(request) self.assertEqual(result.data['request_data'], {'hello': 'world'}) self.assertEqual(result.data['header_github_event'], '') 

Which passes :D

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.