5

In setting up a ArchiveIndexView in Django I am able to successfully display a list of items in a model by navigating to the page myself.

When going to write the test in pytest to verify navigating to the page "checklist_GTD/archive/" succeeds, the test fails with the message:

>       assert response.status_code == 200
E       assert 301 == 200
E        +  where 301 = <HttpResponsePermanentRedirect status_code=301, "text/html; charset=utf-8", url="/checklist_GTD/archive/">.status_code

test_archive.py:4: AssertionError

I understand there is a way to follow the request to get the final status_code. Can someone help me with how this done in pytest-django, similar to this question? The documentation on pytest-django does not have anything on redirects. Thanks.

Scott Skiles
  • 2,933
  • 2
  • 26
  • 50
  • 1
    Does this help? https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.SimpleTestCase.assertRedirects – tdsymonds Jan 09 '18 at 11:20
  • Using 'assertRedirects' might be a good solution to try... but that would involve mixing in "test" from django rather than using an existing fixture from 'pytest-django.' If that is the only way, I wouldn't be surprised! But I'm looking to see if I'm missing something really easy like just adding a decorator to the function. – Scott Skiles Jan 09 '18 at 11:23

2 Answers2

9

pytest-django provides both an unauthenticated client and a logged-in admin_client as fixtures. Really simplifies this sort of thing. Assuming for the moment that you're using admin_client because you just want to test the redirect as easily as possible, without having to log in manually:

def test_something(admin_client):
    response = admin_client.get(url, follow=True)
    assert response.status_code == 200

If you want to log in a standard user:

def test_something(client):
    # Create user here, then:
    client.login(username="foo", password="bar")
    response = client.get(url, follow=True)
    assert response.status_code == 200

By using follow=True in either of these, the response.status_code will equal the return code of the page after the redirect, rather than the access to the original URL. Therefore, it should resolve to 200, not 301.

I think it's not documented in pytest-django because the option is inherited from the Django test client it subclasses from (making requests).

shacker
  • 12,421
  • 6
  • 74
  • 80
  • Do you have any idea why this 'follow' parameter is not in the pytest-django documentation? If you noticed above in my question, I referenced the fact that I didn't see anything on redirection in the documentation, and that is still the case. http://pytest-django.readthedocs.io/en/latest/helpers.html?highlight=get – Scott Skiles Mar 21 '18 at 11:38
  • Edited the answer to include reason it's not documented. But it would be nice to see it documented - I've contributed doc patches to pytest-django before and they've been accepted. In any case, if you found my answer helpful, please consider marking it accepted, or upvoting. – shacker Mar 21 '18 at 15:24
  • Your answer is great, and very close to what I was looking for. Let's see if we can edit it a bit more if possible. In my answer below, I specified what I expected the redirect URL to be. Is there a way to do that with the follow parameter? For example, "final_url" or something (e.g., response = admin_client.get(url, follow=True, final_url="/expected_redirect/url")? ) This will be useful if you want to ensure you properly redirected an unauthenticated client to the login page, and not to another page they should not have access to. – Scott Skiles Mar 22 '18 at 04:19
  • Also, you're missing a closing double quote (") after bar below (won't let me make this trivial edit) – Scott Skiles Mar 22 '18 at 04:20
  • @ScottSkiles just wondering if you found a way to assert the redirect in pytest only, using something like assertRedirect? or did you end up importing from SimpleTestCase? – bmeyer71 Aug 13 '18 at 18:48
  • 1
    @ScottSkiles I actually got what I was looking for in the comments of this answer https://stackoverflow.com/a/48293715/496406 – bmeyer71 Aug 13 '18 at 19:31
  • For the record I still think there is a problem with this answer, where you can set `follow=True` and get a response code of 200 *but not at the URL you expect.* For example, you could get redirected unexpectedly to the login page, follow, and get a response code of 200 when you land on "login" or whatever when you expected something else. – Scott Skiles May 30 '19 at 13:20
0

UPDATE: I'm getting downvoted into oblivion but I still think my answer is better so let me explain.

I still think there is a problem with Shacker's answer, where you can set follow=True and get a response code of 200 but not at the URL you expect. For example, you could get redirected unexpectedly to the login page, follow and get a response code of 200.

I understand that I asked a question on how to accomplish something with pytest and the reason I'm getting downvoted is because I provided an answer using Django's built-in TestCase class. However, the correct answer for the test is/was more important to me at the time than exclusively using pytest. As noted below, my answer still works with pytest's test discovery so I think the answer is still valid. After all, pytest is built upon Django's built-in TestCase. And my answer asserts the response code of 200 came from where I expected it to come from.

The best solution would be to modify pytest to include the expected_url as a parameter. If anyone is up for doing this I think it would be a big improvement. Thanks for reading.

ORIGINAL CONTENT:

Answering my own question here. I decided to include final expected URL using the built-in Django testing framework's assertRedirects and verify that it (1) gets redirected initially with 302 response and (2) eventually succeeds with a code 200 at the expected URL.

from django.test import TestCase, Client

def test_pytest_works():
    assert 1==1

class Test(TestCase):
    def test_redirect(self):
        client = Client()
        response = client.get("/checklist_GTD/archive/")
        self.assertRedirects(response, "/expected_redirect/url", 302, 200)

Hat tip to @tdsymonds for pointing me in the right direction. I appreciated Shacker's answer but I have seen in some scenarios the redirect result being 200 when the page is redirected to an undesirable URL. With the solution above I am able to enforce the redirect URL, which pytest-django does not currently support.

Please note: This answer is compliant with the auto-discover feature of pytest-django and is thus not incompatible (it will auto-discover both pytest-django and Django TestCase tests).

Scott Skiles
  • 2,933
  • 2
  • 26
  • 50
  • 1
    Downvoted because providing a "Just use Django native tests" for a person asking about a pytest question is not particularly helpful. – shacker Mar 21 '18 at 05:55
  • 1
    Hi Shacker. Thanks for the explanation. I answered my own question if you didn't notice, due to the inactivity. And it still solves the problem because Django's built-in testing is compatible with the pytest command. Thanks for your answer, also! I'll check to see if it works. – Scott Skiles Mar 21 '18 at 11:26
  • For fear of being downvoted I won't make this an "answer". But until pytest-django fully supports the HTTP redirect feature of SimpleTestCase I just append the urls with something like assert response.url == reverse('login') + '?next=' + reverse('my_app:create'). – highpost Oct 08 '19 at 22:05