Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django or Django Rest Framework can't resolve url param when testing

I am using Django 1.8.4 and DRF 3.2.1 and when I run requests against the specified urls everything works fine, but when I run the tests with py.test the function update doesn't enter with the task_id param. But the url is fine.

Attached some code from urls.py and views.py and tests.py .. this is an excerpt of the code of course a lot of stuff is missing I just need eyes who can see if I am doing something wrong.

urls.py

from django.conf import settings
from django.conf.urls import include, patterns, url
from rest_framework import routers

from remotetask import views as rt_views

remotetask_detail = rt_views.RemoteTaskViewSet.as_view({'list': 'detail',
                                                       'put': 'update'})
remotetask_all = rt_views.RemoteTaskViewSet.as_view({'list': 'list'})

urlpatterns = patterns(
    '',
    url(r'^remotetasks/$', remotetask_all, name='api-remotetask-all'),
    url(r'^remotetasks/(?P<task_id>\d+)/$', remotetask_detail,
        name='api-remotetask-detail'),
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
)

views.py

from django.shortcuts import get_object_or_404

from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response

from remotetask.models import RemoteTask                                                                                                        
from remotetask.serializers import RemoteTaskSerializer

from rest_framework import viewsets

class RemoteTaskViewSet(viewsets.ViewSet):                                                                                        
    queryset = RemoteTask.objects.all()                                                                                                         
    serializer_class = RemoteTaskSerializer                                                                                                     

    def detail(self, request, task_id=None):
        task = get_object_or_404(RemoteTask, pk=task_id)
        serializer = RemoteTaskSerializer(task)

        return Response(serializer.data)


    def update(self, request, task_id=None):
        task = get_object_or_404(RemoteTask, pk=task_id)
        new_status = request.data.get('status')

        status_changed = task.change_status(new_status, stdout, stderr)

        if status_changed:
            response_status = status.HTTP_201_CREATED
        else:
            response_status = status.HTTP_400_BAD_REQUEST

        serializer = RemoteTaskSerializer(task)

        return Response(serializer.data, status=response_status)

And finally test_views.py

import pytest

from django.core.urlresolvers import reverse

from remotetask.factories import RemoteTaskFactory
from remotetask.models import RemoteTask
from remotetask.views import RemoteTaskViewSet

import json

@pytest.fixture()
@pytest.mark.django_db
def create_remotetask():
    remotetask = RemoteTaskFactory.create()
    return remotetask

@pytest.fixture()
@pytest.mark.django_db()
def clean_remotetask():
    RemoteTask.objects.all().delete()


@pytest.fixture()
def rq_remotetasklist(rf):
    url = reverse('api-remotetask-all')
    request = rf.get(url)
    response = RemoteTaskViewSet.as_view({'list': 'list'})(request)
    return response

@pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
@pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
    response = rq_remotetasklist
    result = response.data.get('results')
    id_to_work = result[0]['id']
    rt = RemoteTask.objects.get(pk=id_to_work)
    assert rt.status == 0
    # new request
    url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
    params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})

    request = rf.put(url, data=params,
                     content_type='application/json')

    new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)

    assert new_response.status_code == 200

By default when a new task is created it gets status 0, so I try to change the status to 2 and it fails, doing some debugging I found that is entering on the update function on RemoteTaskViewSet but is not getting the task_id.

I've followed lots of tutorials and changed back and forth the code and still having the same issue, luckily works in production but it worries to me that I cannot make it run test cases from this code.

The error output from py.test is this:

E       assert 404 == 200
E        +  where 404 = <rest_framework.response.Response object at 0x7f9f465ae690>.status_code

I put a debugger into the update function, seems that task_id is None, but when I print request.stream the url is /api/remotetasks/1/ 1 should be the task_id but isn't getting it, I was about to open a ticket on djangoproject but I think isn't a django bug since it works with external client, this must be something on my code, or anything else.

Update: If I use client instead rf and comment the line where I assign new_response with the call of the method, and validate directly the against request.status_code it works!!!.

Something like this:

@pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
@pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, client):
    response = rq_remotetasklist
    result = response.data.get('results')
    id_to_work = result[0]['id']
    rt = RemoteTask.objects.get(pk=id_to_work)
    assert rt.status == 0
    # new request
    url = reverse('api-remotetask-detail', kwargs={'task_id': id_to_work})
    params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})

    request = client.put(url, data=params,
                         content_type='application/json')

    assert request.status_code == 201

Now the doubt is why it doesn't work in the previous way?

like image 642
Angel Velásquez Avatar asked Nov 09 '22 05:11

Angel Velásquez


1 Answers

The issue (as noted in the updated) is in the request assignment:

request = rf.put(url, data=params,
                 content_type='application/json')

new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)

assert new_response.status_code == 200

There really is no need to do the custom call to the view. That's already being done by the request assignment. It's not working in the old test because that's not how the view gets called when it's routed through the url routes.

Most importantly in the pervious code the request object is not a request, it's the response to the call. Using the old code I believe this would have worked as well:

@pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
@pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
    response = rq_remotetasklist
    result = response.data.get('results')
    id_to_work = result[0]['id']
    rt = RemoteTask.objects.get(pk=id_to_work)
    assert rt.status == 0
    # new request
    url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
    params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})

    response = rf.put(url, data=params,
                     content_type='application/json')

    assert response.status_code == 200
like image 52
Garry Polley Avatar answered Dec 09 '22 02:12

Garry Polley