get router url name when testing in Django Rest Framework

Question:

I have some problem. I use routers in Django Rest Framework and I want to test some api methods.

In urls.py:

router = DefaultRouter()
router.register(r'my-list', MyViewSet, base_name="my_list")

urlpatterns = [
    url(r'^api/', include(router.urls,
                          namespace='api'), ),

]

So, in tests.py I want to use something like reverse. Now I use

response = self.client.get('/api/my-list/')

Its a hard coded string, if I use :

response = self.client.get(reverse('api:my_list')

I have an error:

django.core.urlresolvers.NoReverseMatch: Reverse for 'my_list' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []

How to fix that?

Thanks!

Asked By: Leon

||

Answers:

DRF adds suffixes in viewsets for different URLs – list, detail and possibly custom URLs. You can see that in source code and in docs. So in your case the actual reverse should be something like:

reverse('api:my_list-list')    # for list URL. e.g. /api/my-list/
reverse('api:my_list-detail')  # for detail URL. e.g. /api/my-list/<pk>/

That is why its also probably better to use a resource name as a router base_name. For example base_name='user' vs base_name='users_list'.

Answered By: miki725

Update 2021.

I have added more details from from @miki725 answer.

There are some details that needs to have some considerations such as app_name parameter that need to be placed within the myappname.urls.

Therefore the urls.py should look like this:

# django imports
from django.urls import path, include

# drf imports
from rest_framework import routers

from myappname.viewsets import UserViewSet

# In the example used in the question the app_name is 'api'
app_name = 'myappname' # <---- Needed when testing API URLS..

router = routers.DefaultRouter()
router.register(r'users', UserViewSet, basename='user') # <---- Here already have -list and -detail by default.


urlpatterns = [
    path('', include(router.urls)),
]

tests.py

from myappname.models import User

from django.urls import reverse
from django.utils import timezone

from rest_framework import status
from rest_framework.test import APITestCase


class TestApi(APITestCase):

    def setUp(self):
        self.headerInfo = {'content-type': 'application/json'}
        # example of query to be used on URL.
        self.user = User.objects.create(
           username = 'anyname',
           created_at=timezone.now(),
           created_by='testname',
       )
       self.user.save()
   
       # payload to be used to test PUT method for example...
       self.user_data = {
           'username': 'othername',
           'created_at': timezone.now(),
           'created_by': 'othertestname'
       }

       # again: In the example used in the question the app_name is 'api'
       # that's why reverse('api:my-list')...
       self.url_user_list = reverse('myappname:user-list') # <------
       self.url_user_detail = reverse('myappname:user-detail',
                                       kwargs={'pk': self.user.pk}) # <------

    """
    Test User endpont.
    """
    def test_get_user(self):
        """GET method"""
        response = self.client.get(self.url_user_list, 
        self.user_data, 
        format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_create_user(self):
        """ test POST method for User endpoint"""
        response = self.client.post(
            self.url_user_list, self.user_data,
            # headers=self.headerInfo,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_update_user(self):
        data = {
           'username': 'someothername',
           'created_at': timezone.now(),
           'created_by': 'someothertestname'
        }
        response = self.client.put(self.url_user_detail, data, headers=self.headerInfo)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

Answered By: Elias Prado