5

I just started learning unittests and stuck with this problem.

I got project structure like this (it’s Django 1.6.2 now):

./manage.py
./myproject
./myproject/urls.py
./myproject/myapp/
./myproject/myapp/urls.py
./myproject/myapp/views.py
./tests/
./test/test_example.py

In the ./myproject/urls.py I have:

from django.conf.urls import patterns, include, url
urlpatterns = patterns('',
    url(r'^myapp/', include('myproject.myapp.urls')),
)

In the ./myproject/myapp/urls.py I have:

from django.conf.urls import patterns, url

urlpatterns = patterns('myproject.myapp.views',
    url(r'^example1/$', 'itemlist'),  
    url(r'^example1/(?P<item_id>\w+)/$', 'item'),
)

I wrote basic test and put it into ./test/test_example.py

import unittest
from django.test import Client

class PagesTestCase(unittest.TestCase): 
    def setUp(self):
        self.client = Client()

    def test_itemlist(self):        
        response = self.client.get('/myapp/example1/')
        self.assertEqual(response.status_code, 200)

    def test_item(self):        
        response = self.client.get('/myapp/example1/100100/')
        self.assertEqual(response.status_code, 200)

I run this tests from shell like this:

cd ./tests
python manage.py test

First test runs OK, but he second always fails with ‘404 not found’ status code.

Both urls are working OK in the browser.

Also, I tried this:

cd ./
python manage.py shell
>>> from django.test.client import Client
>>> c = Client()
>>> r = c.get('/myapp/example1/100100/')
>>> r.status_code
200

I just can’t figure out how to run those tests properly. It seems no pattern that is passed into views as parameter ever works for me. But all fixed urls are found correctly by django.test.client.

Thank you!

EDIT: I just found that 404 fires in my myproject/myapp/views.py

There is a code:

def item(request, item_id):
    try:
        item = Item.objects.get(pk = int(item_id))
    except (ValueError, Item.DoesNotExist):     
        raise Http404

And here goes the Item.DoesNotExist exception. I have no any idea, why that item not found?

anti1869
  • 1,219
  • 1
  • 10
  • 18
  • Do you have any items in your database? If not, you should probably insert one or more as test fixtures. (Your test databases are separate from your production databases, so don't worry about blowing away any real data.) – Platinum Azure Jan 27 '15 at 15:18
  • > test databases are separate from your production databases Aaargh! Got it. So that unittest thing has slightly different purpose from what I think it was. It's not for testing errors in production data. Silly me :) – anti1869 Jan 27 '15 at 15:21
  • Right. Unit tests are for testing errors in your code. That said, "errors in your code" could and should include making sure the code behaves sensibly in the face of unavailable or corrupt data, so there is *absolutely* some value in writing a unit test which specifically tests, given that an item with the specified ID does not exist in the database, your view should raise `Http404` (assuming that is your desired/expected behavior in that particular case). – Platinum Azure Jan 27 '15 at 15:59

2 Answers2

2

In addition to using reverse, you can get a 404 if the expected model isn't available in the test database (as mentioned in one of the comments). You should also use Django's TestCase instead of python's unittest since the former inherits from the latter, but makes interacting with the database much easier (among other things).

An example that sets up test data:

from django.test import TestCase
from django.urls import reverse

# Or whatever your object is.
from .models import Item


class ItemViewsTestCase(TestCase):
    """Tests for Item views."""
    @classmethod
    def setUpTestData(cls):
        """Set up test data available for all tests in this class."""
        cls.item = Item.objects.create(name='Testing')

    def test_item_list_view(self):
        # Note, self.client we get for free from Django's TestCase.
        response = self.client.get(reverse('itemlist'))
        self.assertEqual(response.status_code, 200)

    def test_item_detail_view(self):
        # This url pattern expects an Item.id so use the item we set up.
        url = reverse('item', args=[self.item.id])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
Matt Passell
  • 4,549
  • 3
  • 24
  • 39
getup8
  • 6,949
  • 1
  • 27
  • 31
1

Use the reverse() function instead to build URLs, that is:

In ./myproject/myapp/urls.py file give each URL pattern a name parameter that is for example:

from django.conf.urls import patterns, url

urlpatterns = patterns('myproject.myapp.views',
    url(r'^example1/$', 'itemlist', name='example-one'),  
    url(r'^example1/(?P<item_id>\w+)/$', 'item', name='example-two'),
)

We will use the value given to the name parameter to build URLs.

Then in ./test/test_example.py:

from django.core.urlresolvers import reverse

class PagesTestCase(unittest.TestCase): 
    ...
    def test_itemlist(self):
        url = reverse('example-one')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_item(self):
        url = reverse('example-two')    
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

That should do the trick.

lwairore
  • 624
  • 1
  • 7
  • 11