Django Test Client does not create database entries

Question:

I’m creating unit tests for my views using Django’s built-in Test Client to create mock requests.
The view I’m calling should create an object in the database. However, when I query the database from within the test method the object isn’t there – it either hasn’t been created or has been discarded on returning from the view.

Here’s the view:

def apply_to_cmp(request, campaign_id):
    """ Creates a new Application to 'campaign_id' for request.user """
    campaign = Campaign.objects.get(pk = campaign_id)
    if not Application.objects
                      .filter(campaign = campaign, user = request.user)
                      .exists():
        application = Application(**{'campaign' : campaign,
                                     'user'     : request.user})
        application.save()

    return HttpResponseRedirect(request.META.get('HTTP_REFERER'))

This is the test that calls it:

def test_create_campaign_app(self):
    """ Calls the method apply_to_cmp in .views """
    c = Client()
    c.login(username = self.username, password = self.password)
    url = '/campaign/' + self.campaign.id + '/apply/'
    response = c.get(url)

    # Check whether request was successful (should return 302: redirect)
    self.assertEqual(response.status_code, 302)

    # Verify that an Application object was created
    app_count = Application.objects
                .filter(user = self.user, campaign = self.campaign)
                .count()
    self.assertEqual(app_count, 1)

This is the output from the running the test:

Traceback (most recent call last):
  File "/test_views.py", line 40, in test_create_campaign_app
    self.assertEqual(app_count, 1)
AssertionError: 0 != 1

The method apply_to_cmp is definitely being called, since response.status_code == 302, but still the Application object is not created. What am I doing wrong?

Asked By: kreld

||

Answers:

Nothing stands out as particularly wrong with your code – but clearly either your test case or the code your are testing is not working the way you think. It is now time to question your assumptions.

The method apply_to_cmp is definitely being called, since response.status_code == 302

This is your first assumption, and it may not be correct. You might get a better picture of what is happening if you examine other details in the response object. For example, check the response.redirect_chain and confirm that it actually redirects where you expect it to:

response = c.get(url, follow=True)
self.assertEqual(response.redirect_chain, [<expected output here>])

What about other details? I can’t see where self.username and self.password are defined from the code you provided. Are you 100% sure that your test code to login worked? c.login() returns ‘True’ or ‘False’ to indicate if the login was successful. In my test cases, I like to confirm that the login succeeds.

login_success = c.login(username = self.username, password = self.password)
self.assertTrue(login_success)

You can also be a bit more general. You find nothing if you check Application.objects.filter(user=self.user, campaign=self.campaign), but what about checking Application.objects.all()? You know that a specific item isn’t in your database, but do you know what is stored in the database (if anything at all) in the test code at that time? Do you expect other items in there? Check to confirm that what you expect is true.

I think you can solve this one, but you’ll need to be a bit more aggressive in your analysis of your test case, rather than just seeing that your app_count variable doesn’t equal 1. Examine your response object, put in some debug statements, and question every assumption.

Answered By: Aurora

First of all, if you are subclassing from django.test.TestCase, please take in consideration the fact that each test is wrapped into transactions (official docs).

Then, you can add db logging to your project to see whether there was a hit to the database or not (official docs).

And finally be sure that you’re using correct lookups at this line: filter(user = self.user, campaign = self.campaign)

Answered By: Nick Lesnykh

Client.login failed because the login system was not properly initialised in the setUp method. I fixed this by calling call_command('loaddata', 'initial_data.json') with initial_data.json containing the setup for the login system. Also, HttpResponseRedirect(request.META.get('HTTP_REFERER')) didn’t work for obvious reasons. I changed that bit to

if request.META.get('HTTP_REFERER'):
    return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
return HttpResponse()

And therefore the test to

self.assertEqual(response.status_code, 200)

This answer was posted as an edit to the question Django Test Client does not create database entries by the OP kreld under CC BY-SA 3.0.

Answered By: vvvvv