0

Similar to this question, I'm having problems testing a view with Django's unit testing framework. My view is very simple: it processes a form, adds an object to the database, and returns a JSONResponse. The test is equally simple, but I keep getting "First argument is not valid JSON: ''. The code actually works in my application; it just doesn't seem to work when unit testing. Any help is appreciated.

EDIT: Traceback

======================================================================

ERROR: tearDownClass (zoning_intake.tests.AddActionTypeTest)

Traceback (most recent call last): File "C:\Virtual\Django18\lib\site-packages\django\test\testcases.py", line 96 2, in tearDownClass cls._rollback_atomics(cls.cls_atomics) AttributeError: type object 'AddActionTypeTest' has no attribute 'cls_atomics'

====================================================================== FAIL: test_post_add_action_type_succeeds (zoning_intake.tests.AddActionTypeTest)


Traceback (most recent call last): File "C:\Hg\sdcgis\zoning_intake\tests.py", line 26, in test_post_add_action_t ype_succeeds self.assertJSONEqual(response.content,{'result':'Success', 'msg':'Success'})

File "C:\Virtual\Django18\lib\site-packages\django\test\testcases.py", line 68 9, in assertJSONEqual self.fail("First argument is not valid JSON: %r" % raw) AssertionError: First argument is not valid JSON: ''


Ran 1 test in 10.757s

FAILED (failures=1, errors=1) Preserving test database for alias 'default'... Preserving test database for alias 'other'...

My view:

    form = ActionTypeForm(request.POST)
    if form.is_valid():
        action = form.cleaned_data['action']
        new_type = CaseRequestActionType(action=action)
        new_type.save()

        return JsonResponse({'result':'Success', 'msg':'Success'})
    else:
        return JsonResponse({'result':'Fail', 'msg':'An unknown error occurred'})

My test:

class AddActionTypeTest(TestCase):

    if django.VERSION[:2] >= (1, 7):
        # Django 1.7 requires an explicit setup() when running tests in PTVS
        @classmethod
        def setUpClass(cls):
            django.setup()

    def test_post_add_action_type_fails(self):
        response = self.client.post(reverse('zoning:add_action_type'))
        self.assertEqual(response.status_code, 302)
        self.assertJSONEqual(force_text(response.content), {'result':'Fail', 'msg':'An unknown error occurred'})
Shawn
  • 717
  • 11
  • 31
  • Can you update your question and post your full stacktrace? – Shang Wang Dec 07 '15 at 23:13
  • Updated as requested – Shawn Dec 08 '15 at 16:30
  • It occurs to me that perhaps it's because I'm getting a status_code 302 instead of 200, though for the life of me I can't figure out how I'm redirecting in my view nor in my unit test – Shawn Dec 08 '15 at 16:36
  • You should use `pdb` to debug your function in `views.py`, or at least use some `print` statements to see if the view indeed returns the desired json. If everything is OK with `views.py`, print the `response.content` in your test and see what's in there. – Shang Wang Dec 08 '15 at 16:39
  • Also, `302` response might not really be a redirection in your case, you might have errors in your view function. Check this SO post: http://stackoverflow.com/questions/17352314/django-test-client-post-returns-302-despite-error-on-views-post – Shang Wang Dec 08 '15 at 16:42
  • That is indeed the redirect and probably the underlying problem. I have login_required decorator on the view and I'm not setting up a logged in user before running the test. So the JSON doesn't exist as it's redirecting to the login page, which is why the 302 is returning as well. Thanks for pointing me in the right direction. If you want to write up an answer I'll be glad to accept it. Thanks – Shawn Dec 08 '15 at 16:45
  • Oh, that makes perfect sense. You found the answer yourself so why don't you edit your question by adding the function signature, and you can answer it yourself. Anyway, glad that the problem is solved. – Shang Wang Dec 08 '15 at 16:50

1 Answers1

2

So it turns out that the issue is very simple, and the 302 status code is the key to understanding why I had this issue. I have the @login_required decorator on my view, so when I ran the test WITHOUT having logged in a user, I'm redirected to my login view. Since the login view returns html, not JSON, my response is not valid JSON, and the status code returns 302 instead of the expected 200. I needed to override the setUp method to create a user in the database and then call login in the test itself in order for my test to work properly and my status code to return 200. Thanks to @Shang Wang for assistance

Complete View:

@login_required
def add_action_type(request):
    if request.method == 'GET':
        ...
    else:
        form = ActionTypeForm(request.POST)
        if form.is_valid():
            action = form.cleaned_data['action']
            new_type = CaseRequestActionType(action=action)
            new_type.save()

            return JsonResponse({'result':'Success', 'msg':'Success'})
        else:
            return JsonResponse({'result':'Fail', 'msg':'An unknown error occurred'})

Updated test:

class AddActionTypeTest(TestCase):

    @classmethod
    def setUp(self):
        self.user = User.objects.create_user(username='shawn', email='shawn@...com', password='top_secret')

    def test_post_add_action_type_fails(self):
        self.client.login(username=self.user.username, password='top_secret')
        response = self.client.post(reverse('zoning:add_action_type'))
        self.assertJSONEqual(force_text(response.content), {'result':'Fail', 'msg':'An unknown error occurred'})
Shawn
  • 717
  • 11
  • 31