My test response is a 302 response which doesn't have a content and can't figure out how to fix it!

Here is my test:

from django.urls import reverse
from django.test import TestCase
from django.urls import resolve
from django.contrib.auth.models import User
from ..views import index, board_topics, new_topic
from ..models import Board, Topic, Post
from ..forms import NewTopicForm

class NewTopicTests(TestCase):

    def setUp(self):
        self.board = Board.objects.create(name="Python", description="Everything related to Python")
        self.user = User.objects.create_user(username='jane', email='jane@doe.com', password='123')

    def test_csrf(self):
        url = reverse('new_topic', kwargs={'id': 1})
        response = self.client.get(url)
        self.assertContains(response, 'csrfmiddlewaretoken') <- I think this is the culprit

    def test_new_topic_valid_post_data(self):
        url = reverse('new_topic', kwargs={'id': 1})
        data = {
            'subject': 'Test title',
            'message': 'Lorem ipsum dolor sit amet'
        }
        response = self.client.post(url, data)
        self.assertTrue(Topic.objects.exists())
        self.assertTrue(Post.objects.exists())

    def test_new_topic_invalid_post_data(self):
        '''
        Invalid post data should not redirect
        The expected behavior is to show the form again with validation errors
        '''
        url = reverse('new_topic', kwargs={'id': 1})
        response = self.client.post(url, {})
        self.assertEqual(response.status_code, 200)

    def test_new_topic_invalid_post_data_empty_fields(self):
        '''
        Invalid post data should not redirect
        The expected behavior is to show the form again with validation errors
        '''
        url = reverse('new_topic', kwargs={'id': 1})
        data = {
            'subject': '',
            'message': ''
        }

And here is the output to Terminal:

Found 4 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F..F
======================================================================
FAIL: test_csrf (boards.tests.test_new_topic_tests.NewTopicTests.test_csrf)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/mariacam/Python-Development/django-boards/django_boards/boards/tests/test_new_topic_tests.py", line 18, in test_csrf
    self.assertContains(response, 'csrfmiddlewaretoken')
  File "/Users/mariacam/.pyenv/versions/3.12.5/lib/python3.12/site-packages/django/test/testcases.py", line 609, in assertContains
    text_repr, real_count, msg_prefix, content_repr = self._assert_contains(
                                                      ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mariacam/.pyenv/versions/3.12.5/lib/python3.12/site-packages/django/test/testcases.py", line 571, in _assert_contains
    self.assertEqual(
AssertionError: 302 != 200 : Couldn't retrieve content: Response code was 302 (expected 200)

======================================================================
FAIL: test_new_topic_valid_post_data (boards.tests.test_new_topic_tests.NewTopicTests.test_new_topic_valid_post_data)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/mariacam/Python-Development/django-boards/django_boards/boards/tests/test_new_topic_tests.py", line 30, in test_new_topic_valid_post_data
    self.assertTrue(Topic.objects.exists())
AssertionError: False is not true

----------------------------------------------------------------------
Ran 4 tests in 0.864s

FAILED (failures=2)
Destroying test database for alias 'default'...

I am new to Django tests. Any feedback would be very appreciated. Thanks!

Your request to the URL is redirecting, rather than returning a normal response.

What’s the content of your urls.py containing the URL, and what’s the code for its view?

Hi @philgyford thanks so much for the reply! The content of my urls.py containing the new_topic url is:

path("boards/<str:id>/", views.board_topics, name="board_topics"),
    path("boards/<str:id>/new/", views.new_topic, name="new_topic"),
    path("boards/<id>/topics/<topic_id>/", views.topic_posts, name="topic_posts"),
    path("boards/<id>/topics/<topic_id>/reply/", views.reply_topic, name="reply_topic"),

I only included what I thought relevant (urls.py is fairly large). And the new_topic view:

@login_required
def new_topic(request, id):
    board = get_object_or_404(Board, id=id)
    if request.method == 'POST':
        form = NewTopicForm(request.POST)
        if form.is_valid():
            topic = form.save(commit=False)
            topic.board = board
            topic.starter = request.user  # <- here
            topic.save()
            Post.objects.create(
                message=form.cleaned_data.get('message'),
                topic=topic,
                created_by=request.user  # <- and here
            )
            return redirect('topic_posts', id=id, topic_id=topic.id)  # redirect to the created topic page
    else:
        form = NewTopicForm()
    return render(request, 'new_topic.html', {'board': board, 'form': form})

I think that because the view has @login_required, you’re getting a redirect to the login page, because your tests don’t use a logged-in user.

If you do self.client.force_login(user=self.user) before you make a request, that might fix things.

But it is also worth doing tests for these views without logging in, and checking that you get a redirect to the login page, or a 401 Unauthorised status, depending on what’s expected - good to check that log in is required where you expect it to be.

Thanks @philgyford> I will try and do this. I’ll share how it goes!