· 

Django: tests.py

1. Code

I wrote /polls/tests.py, which is based on Writing your first Django app, part 5.

Here is part of the code:

class QuestionIndexViewTests(TestCase):

    def setUp(self):

        self.user = User.objects.create_user(username='testuser', password='testpass')

        self.client.login(username='testuser', password='testpass')  # ✅ log in the test user

    def test_no_questions(self):

        """

        If no questions exist, an appropriate message is displayed.

        """

        response = self.client.get(reverse("polls:index"))

        self.assertEqual(response.status_code, 200)

        self.assertContains(response, "No polls are available.")

        self.assertQuerySetEqual(response.context["latest_question_list"], [])

    def test_past_question(self):

        """

        Questions with a pub_date in the past are displayed on the

        index page.

        """

        question = create_question(question_text="Past question.", days=-30)

        response = self.client.get(reverse("polls:index"))

        self.assertQuerySetEqual(

            response.context["latest_question_list"],

            [question],

        )

    def test_future_question(self):

        Questions with a pub_date in the future aren't displayed on

        """

        Questions with a pub_date in the future aren't displayed on

        the index page.

        """

        create_question(question_text="Future question.", days=30)

        response = self.client.get(reverse("polls:index"))

        self.assertContains(response, "No polls are available.")

        self.assertQuerySetEqual(response.context["latest_question_list"], [])

 

    def test_future_question_and_past_question(self):

        """

        Even if both past and future questions exist, only past questions

        are displayed.

        """

        question = create_question(question_text="Past question.", days=-30)

        create_question(question_text="Future question.", days=30)

        response = self.client.get(reverse("polls:index"))

        self.assertQuerySetEqual(

            response.context["latest_question_list"],

            [question],

        )

 

    def test_two_past_questions(self):

        """

        The questions index page may display multiple questions.

        """

        question1 = create_question(question_text="Past question 1.", days=-30)

        question2 = create_question(question_text="Past question 2.", days=-5)

        response = self.client.get(reverse("polls:index"))

        self.assertQuerySetEqual(

            response.context["latest_question_list"],

            [question2, question1],

        )

 

 

2. Conversation with ChatGPT

The following is part of polls/tests.py.  How can I use this function?  What outcome will be created by this?  

def create_question(question_text, days):

    """

    Create a question with the given question_text and published the

    given number of days offset to now (negative for questions published

    in the past, positive for questions that have yet to be published).

    """

    time = timezone.now() + datetime.timedelta(days=days)

    return Question.objects.create(question_text=question_text, pub_date=time)

 

The create_question() function is a helper function designed to simplify creating Question objects for testing. Here's a full explanation and example of how to use it.


✅ What the function does:

python
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
  • It takes two arguments:

    • question_text: the text of the question.

    • days: how many days to offset the publish date from now.

      • Negative = past (e.g., -30 means 30 days ago).

      • Positive = future (e.g., +5 means 5 days from now).

  • It creates and saves a Question object with that text and publish date.


✅ How to use it in a test or in the Django shell:

In a unit test:

python
def test_future_question_is_not_displayed(self):
create_question("Future question", 30)
response = self.client.get(reverse("polls:index"))
self.assertNotContains(response, "Future question")

In the Django shell:

bash
python manage.py shell

Then:

python
from polls.tests import create_question
q = create_question("Is this a test?", -1)
print(q.id, q.question_text, q.pub_date)

✅ What outcome will be created?

If you run:

python
create_question("What is your name?", -7)

Then it will:

  • Create a question titled "What is your name?".

  • Set pub_date to 7 days ago from the current time.

  • Save it to the database.

  • Return the Question object.


 

Let me know if you'd like to also create associated Choice entries for the question!

 

3. Another conversation

Do I need to delete the created Question object after testing?

 

Great question!

✅ Short Answer:

No, you usually do not need to delete the created Question object manually after testing — if you're using Django's TestCase.


🔍 Why?

When you're writing tests using django.test.TestCase, Django automatically:

  • Uses a temporary test database.

  • Rolls back all database changes after each test method.

  • So every test starts with a clean database.


✅ Example:

python
class QuestionModelTests(TestCase):
def test_question_creation(self):
q = create_question("Temporary test question", -1)
self.assertEqual(q.question_text, "Temporary test question")

This Question object is only available during this test. Once the test finishes, it’s automatically deleted when the test database is reset.


❗Exception: If You're in the Django Shell or Real App

If you use create_question() in the Django shell or in the actual app, the Question object is saved in your real database. In that case:

  • It won’t be automatically deleted.

  • You can delete it manually if needed:

    python
    q.delete()

 

Let me know if you want a quick cleanup script for the shell too!

 

 

4. Another Conversation(2)

Does "a temporary test database" mean there will be a new database separate from the original sqlite database I am using?

 

Yes, exactly!

✅ In Django TestCase, a temporary test database means:

Django creates a separate, temporary database just for running tests — completely separate from your real database like db.sqlite3.


🔍 How it works:

  • Before running your tests, Django creates a new empty database (in memory or a temp file).

  • It applies all migrations to this temp database.

  • Tests run using this test database only.

  • After the tests finish, Django deletes the test database automatically.


🔒 Why this is safe and useful:

  • Your real data in db.sqlite3 stays untouched.

  • Tests can freely create, modify, or delete records.

  • You don’t have to worry about cleaning up test data.


🛠️ Example log when running tests:

You might see something like this:

bash
Creating test database for alias 'default'... ... Destroying test database for alias 'default'...

That shows the test database is being created and destroyed automatically.

 

Let me know if you want to actually see this behavior or try a custom test to verify it!

 

5. Another Conversation (3)

Can you explain the 3rd line of this code?  Does "response" get an HTML file?

def test_future_question_is_not_displayed(self):

    create_question("Future question", 30)

    response = self.client.get(reverse("polls:index"))

    self.assertNotContains(response, "Future question")

 

Yes, let's break it down line by line — especially the 3rd line as you asked:

python
def test_future_question_is_not_displayed(self):
create_question("Future question", 30) # 1️⃣ Creates a question dated 30 days in the future
response = self.client.get(reverse("polls:index")) # 2️⃣ Makes a simulated HTTP GET request
self.assertNotContains(response, "Future question") # 3️⃣ Asserts that the future question is NOT in the HTML

🔍 Line 3 explanation:

python
response = self.client.get(reverse("polls:index"))

What it does:

  • self.client.get(...) simulates a browser sending a GET request to the URL for the polls:index view.

  • reverse("polls:index") returns the actual URL path for the named URL (polls:index) — typically something like "/polls/".

  • response captures the full HTTP response from that request, including:

    • response.status_code (e.g., 200 OK)

    • response.content (the actual HTML as bytes)

    • response.context (data sent to the template)

✅ So yes — the variable response will contain the HTML page returned by the view at polls:index.


 

Let me know if you want to inspect that HTML (response.content) or test more conditions!

 

6. Another Conversation (4)

What does this code do?

class QuestionIndexViewTests(TestCase):

    def test_no_questions(self):

        """

        If no questions exist, an appropriate message is displayed.

        """

        response = self.client.get(reverse("polls:index"))

        self.assertEqual(response.status_code, 200)

        self.assertContains(response, "No polls are available.")

        self.assertQuerySetEqual(response.context["latest_question_list"], [])

 

This Django test case checks the behavior of the polls index page when there are no questions in the database. Let’s go through the code line by line:


🔍 Code Breakdown:

python
class QuestionIndexViewTests(TestCase):

This defines a test class that inherits from Django’s TestCase, which sets up a temporary test database for each test method.


python
def test_no_questions(self):

This is a test method. Django will automatically run it because its name starts with test_.


python
""" If no questions exist, an appropriate message is displayed. """

This is a docstring describing what the test is supposed to check.


python
response = self.client.get(reverse("polls:index"))
  • Uses Django's test client to simulate a GET request to the index page of the polls app.

  • reverse("polls:index") resolves the URL for the index view in the polls namespace.


python
self.assertEqual(response.status_code, 200)
  • Asserts that the HTTP response status is 200 OK, meaning the page loaded successfully.


python
self.assertContains(response, "No polls are available.")
  • Checks that the phrase "No polls are available." appears somewhere in the HTML response.

  • This confirms that the proper message is shown when there are no poll questions.


python
self.assertQuerySetEqual(response.context["latest_question_list"], [])
  • Checks that the latest_question_list variable passed to the template is an empty list.

  • This ensures that the view logic correctly returns an empty queryset when there are no questions in the database.


✅ Summary:

This test confirms that when there are no questions in the database, the polls index page:

  1. Loads correctly,

  2. Shows the message "No polls are available.",

  3. Passes an empty list to the template context.


 

Would you like to see how to add a test when there is one question?

 

7. Another Question (5)

Where is "context_object" defined?

class IndexView(LoginRequiredMixin, generic.ListView):

    template_name = "polls/index.html"

    context_object_name = "latest_question_list"

    def get_queryset(self):

        #Return the last five published questions (not including future ones).

        return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[:5]

 

Great question!

In your code:

python
class IndexView(LoginRequiredMixin, generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"

🔍 What is context_object_name?

  • It is not a variable you're defining from scratch — it is a predefined attribute of Django’s ListView class (which you are inheriting from via generic.ListView).

  • Setting it changes the name of the context variable that the view passes to the template.


🔧 By default:

If you don’t specify context_object_name, Django uses the default name:

python
object_list

So this would be your context in the template:

django
{% for object in object_list %}

But by writing:

python
context_object_name = "latest_question_list"

You're telling Django:

"When rendering the template, pass the list of questions using the context variable latest_question_list instead of the default object_list."


✅ Where is context_object_name defined?

It is defined in Django's ListView class here:

python
context_object_name: Optional[str] = None

Source: django.views.generic.list.ListView


🔁 So in your template:

django
{% for question in latest_question_list %}

...will work correctly because of context_object_name = "latest_question_list".


 

Let me know if you'd like to trace the full inheritance path of ListView or see what else you can customize.

 

8. Another Conversation 

Where can Django put questions into latest_question_list?

 

Excellent follow-up!

Django puts questions into latest_question_list (or any context variable you define with context_object_name) automatically by:


✅ 1. Calling Your View’s get_queryset() Method

In your class-based view:

python
class IndexView(LoginRequiredMixin, generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[:5]
  • Django calls get_queryset() internally when the view is accessed.

  • This returns a QuerySet of Question objects (e.g. 5 most recent ones).


✅ 2. Assigning the QuerySet to the Context

Because you wrote:

python
context_object_name = "latest_question_list"

Django puts the result of get_queryset() into the context like this (internally):

python
context["latest_question_list"] = self.get_queryset()

This is how the list of questions becomes available to your template as {{ latest_question_list }}.


🧠 Summary:

  • get_queryset() provides the data.

  • context_object_name tells Django what variable name to use in the template.

  • Django automatically builds the context and injects it when rendering the template.


 

Let me know if you'd like to see what happens if you don’t specify context_object_name, or want to customize the context further with get_context_data().

9. Another Conversation

How can I run this test?

 

To run your Django test (such as the one in polls/tests.py), follow these steps:


1. Make Sure You're in the Django Project Directory

You should see manage.py in the current folder.

bash
cd /path/to/your/project # e.g. cd ~/django_projects/mysite/

2. Run the Tests Using manage.py

▶ Run all tests for your project:

bash
python manage.py test

▶ Run only the tests in the polls app:

bash
python manage.py test polls

▶ Run a specific test case or method:

If you want to run a specific test method like test_no_questions() inside QuestionIndexViewTests, run:

bash
python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions

💡 Replace the method/class name with the one you want to test.


3. Read the Output

If the test passes, you'll see something like:

bash
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

If the test fails, Django will print details showing:

  • What failed

  • What was expected

  • What was actually received


 

Would you like help interpreting any test output?

 

10. Another Conversation

If I ran python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions, is a "no-question" case created?

 

Yes — when you run this test:

bash
python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions

a temporary, isolated test database is created automatically, and in that database, no questions exist unless you add them inside the test. So in your case:

Here's what happens:

python
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])

🧠 Explanation:

  • Since you don’t create any Question in this test, the test database will have zero questions.

  • This simulates the "no questions" scenario.

  • The test checks that:

    • The HTTP response is 200.

    • The page contains "No polls are available."

    • The list of latest questions is empty: [].


✅ Summary:

Yes, when you run that test, it creates a clean test environment (no data), and no questions exist unless explicitly created in the test code. That’s why this test correctly verifies how the app behaves when there are no questions.

 

Let me know if you want to see how to create questions in the test or simulate other cases!

 

11. Another Conversation

I ran it and I got this error. Why?

Traceback (most recent call last):

  File "/home/mtag79/django_projects/mysite/polls/tests.py", line 53, in test_no_questions

    self.assertEqual(response.status_code, 200)

    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^

AssertionError: 302 != 200

 

Great question — this error:

yaml
AssertionError: 302 != 200

means your view redirected the request instead of returning a normal response.


🔍 What does 302 mean?

  • 302 is an HTTP status code for Redirect.

  • This usually happens when a view is protected by LoginRequiredMixin, but the test client is not logged in.


✅ What’s likely happening in your case

Your IndexView looks like this:

python
class IndexView(LoginRequiredMixin, generic.ListView):

This means users must be logged in to see this view.

But in your test:

python
response = self.client.get(reverse("polls:index"))

You're sending a request without logging in. So Django redirects the user to the login page302.


💡 How to fix it

You need to log in a test user before calling the view.

Add this to the beginning of your test:

python

from django.contrib.auth.models import User

 

class QuestionIndexViewTests(TestCase):

    def setUp(self):

        self.user = User.objects.create_user(username='testuser', password='testpass')

 

    def test_no_questions(self):

        self.client.login(username='testuser', password='testpass')  # ✅ log in

 

        response = self.client.get(reverse("polls:index"))

        self.assertEqual(response.status_code, 200)

        self.assertContains(response, "No polls are available.")

        self.assertQuerySetEqual(response.context["latest_question_list"], [])

 

✅ Summary

  • 302 means your test user is being redirected to login.

  • Fix it by using self.client.login(...) in the test.

 

Let me know if you'd like help creating more test users or testing logged-out behavior!

 

12. Another Conversation

Am I right to understand that testuser is created temporarily and destroyed after the test?

 

Yes, you're absolutely right!

In Django’s test framework, everything inside a test class that inherits from TestCase (like QuestionIndexViewTests) is executed within a temporary test database that is:

✅ Automatically Created and Destroyed

  • Created before your test runs

  • Destroyed immediately after your test finishes


🧪 What this means for your testuser

When you run this in your test:

python
self.user = User.objects.create_user(username='testuser', password='testpass')
  • The testuser is created in a temporary database.

  • It will not affect your real database.

  • After the test run, the user (and any test data) is completely deleted.


🧼 No Manual Cleanup Required

You do not need to delete the user or data manually — Django handles this for you.


 

Let me know if you want to confirm this behavior or explore how Django manages test databases behind the scenes!

 

13. Another Conversation

I ran:

python manage.py test polls.tests.QuestionIndexViewTests

, but I still got a 302 error.  I put

def setUp(self):

        self.user = User.objects.create_user(username='testuser', password='testpass') 

 

You're almost there! The reason you're still getting a 302 (redirect) instead of the expected 200 status code is because the test is hitting a login-required view without logging in.

Even though you've created the user in setUp(), Django doesn't automatically log the test client in.


✅ Fix: Log in the test user in setUp()

You need to explicitly log in the test client. Here's how to fix it:

python

🔁 Why 302 Happens

  • 302 redirect usually means: "This page requires login; please go to /accounts/login/".

  • Since you're using LoginRequiredMixin in your IndexView, the test must simulate a logged-in session.


 

Let me know if you’d like help mocking login for views, bypassing login in tests, or inspecting redirects.

 

14. Another Conversation

It worked.  I also ran:

python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions

and I got it correctly.  Is that mean the setUp() function is executed even if I don't specify it?

 

Yes, exactly! ✅

When you run:

bash
python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions

Django still executes the full lifecycle of the test class QuestionIndexViewTests, which includes:

  1. Calling setUp() before each test method (in this case, just test_no_questions).

  2. Running the test method itself.

  3. Cleaning up after the test.

🔁 How Django's unittest.TestCase lifecycle works

For each test method:

  • setUp() is automatically called before the test method runs.

  • tearDown() (if defined) is automatically called after the test method runs.

This happens even when running a single test, as in your command.


🔍 Bonus tip

If you ever want to verify the order, you can add something like this:

python
def setUp(self):
print("Setting up before test...")
# rest of your setup

You’ll see the message printed when the test runs — confirming that Django calls it.

 

Let me know if you'd like to explore setUpTestData() too — it's a class-level setup that runs only once for all tests in the class.

 

15. Another Conversation

There are three types of assert...().  Can you explain the difference?

def test_no_questions(self):

        """

        If no questions exist, an appropriate message is displayed.

        """

        response = self.client.get(reverse("polls:index"))

        self.assertEqual(response.status_code, 200)

        self.assertContains(response, "No polls are available.")

        self.assertQuerySetEqual(response.context["latest_question_list"], [])

 

Great question! In your test method, you're using three different assert...() methods from Django's test framework (inherited from Python's unittest). Here's what each of them does:


1. self.assertEqual(response.status_code, 200)

Purpose: Verifies that the HTTP response status code is exactly 200 (OK).

  • assertEqual(a, b) checks that a == b.

  • If the values are not equal, the test fails.

📌 In your case:

  • You expect the index page to load normally (200 OK), not redirect or error.


2. self.assertContains(response, "No polls are available.")

Purpose: Checks that a specific string exists in the response.content.

  • It looks for "No polls are available." in the HTML content returned from the view.

  • It also checks that the HTTP status is 200 by default unless you pass status_code=....

📌 This is a Django-specific enhancement over assertIn(...).


3. self.assertQuerySetEqual(response.context["latest_question_list"], [])

Purpose: Compares a Django QuerySet with a Python list (or another QuerySet).

  • It checks if the objects in both match exactly in order and content.

  • response.context["latest_question_list"] should return an empty list if no questions exist.

📌 This is especially useful when testing results returned from the database.


Summary Table

Assertion Checks That... Typical Use
assertEqual(a, b) a == b Numbers, strings, counts
assertContains(response, text) text in response.content HTML/text rendering
assertQuerySetEqual(qs, list) QuerySet matches expected values DB-related results

 

Let me know if you'd like examples or if you'd like to explore assertIn, assertTrue, or assertRaises.