pytest-django: Is this the right way to test view with parameters?
Asked Answered
F

1

5

Say I'm testing an RSS feed view in a Django app, is this how I should go about it?

def test_some_view(...):
    ...
    requested_url = reverse("personal_feed", args=[some_profile.auth_token])
    resp = client.get(requested_url, follow=True)
    ...
    assert dummy_object.title in str(resp.content)
  1. Is reverse-ing and then passing that into the client.get() the right way to test? I thought it's DRYer and more future-proof than simply .get()ing the URL.

  2. Should I assert that dummy_object is in the response this way?

  3. I'm testing here using the str representation of the response object. When is it a good practice to do this vs. using selenium? I know it makes it easier to verify that said obj or property (like dummy_object.title) is encapsulated within an H1 tag for example. On the other hand, if I don't care about how the obj is represented, it's faster to do it like the above.

Foliage answered 8/6, 2019 at 23:27 Comment(5)
1. Using reverse is IMO the correct way - you are testing not against URLs, but against Django views, which are being accessed by name. 2. AFAIK the content is a raw bytestring; it should be decoded using resp.charset first. Usually, I only test status codes of HTML responses in unit tests; the content is better to test with selenium in e2e tests, but for simple pages your check should also suffice.Abut
@Abut - Can you maybe expand on resp.charset and how is selenium different in this context? I know what selenium is used for and that it can make the test cover HTML, etc..., but in case I just want to test whether some obj is in some part/QuerySet, doesn't this save time vs selenium? (maybe add this as an answer so I can mark it)Foliage
Ah, I didn't read your question carefully - indeed, in case of an RSS feed my comment makes no sense since it's not a text/html response :-)Abut
@Abut - still, some insight on when to use this vs. selenium would be golden. Should I ask a different question? or is it suitable here?Foliage
Great. I’ll edit the question slightly.Foliage
A
7

Reevaluating my comment (didn't carefully read the question and overlooked the RSS feed stuff):

  1. Is reverse-ing and then passing that into the client.get() the right way to test? I thought it's DRYer and more future-proof than simply .get()ing the URL.

I would agree on that - from Django point, you are testing your views and don't care about what the exact endpoints they are mapped against. Using reverse is thus IMO the clear and correct approach.

  1. Should I assert that dummy_object is in the response this way?

You have to pay attention here. response.content is a bytestring, so asserting dummy_object.title in str(resp.content) is dangerous. Consider the following example:

from django.contrib.syndication.views import Feed

class MyFeed(Feed):
    title = 'äöüß'
    ...

Registered the feed in urls:

urlpatterns = [
    path('my-feed/', MyFeed(), name='my-feed'),
]

Tests:

@pytest.mark.django_db
def test_feed_failing(client):
    uri = reverse('news-feed')
    resp = client.get(uri)
    assert 'äöüß' in str(resp.content)


@pytest.mark.django_db
def test_feed_passing(client):
    uri = reverse('news-feed')
    resp = client.get(uri)
    content = resp.content.decode(resp.charset)
    assert 'äöüß' in content

One will fail, the other won't because of the correct encoding handling.

As for the check itself, personally I always prefer parsing the content to some meaningful data structure instead of working with raw string even for simple tests. For example, if you are checking for data in a text/html response, it's not much more overhead in writing

soup = bs4.BeautifulSoup(content, 'html.parser')
assert soup.select_one('h1#title-headliner') == '<h1>title</h1>'

or

root = lxml.etree.parse(io.StringIO(content), lxml.etree.HTMLParser())
assert next(root.xpath('//h1[@id='title-headliner']')).text == 'title'

than just

assert 'title' in content

However, invoking a parser is more explicit (you won't accidentally test for e.g. the title in page metadata in head) and also makes an implicit check for data integrity (e.g. you know that the payload is indeed valid HTML because parsed successfully).

To your example: in case of RSS feed, I'd simply use the XML parser:

from lxml import etree

def test_feed_title(client):
    uri = reverse('my-feed')
    resp = client.get(uri)
    root = etree.parse(io.BytesIO(resp.content))
    title = root.xpath('//channel/title')[0].text
    assert title == 'my title'

Here, I'm using lxml which is a faster impl of stdlib's xml. The advantage of parsing the content to an XML tree is also that the parser reads from bytestrings, taking care about the encoding handling - so you don't have to decode anything yourself.

Or use something high-level like atoma that ahs a nice API specifically for RSS entities, so you don't have to fight with XPath selectors:

import atoma

@pytest.mark.django_db
def test_feed_title(client):
    uri = reverse('my-feed')
    resp = client.get(uri)
    feed = atoma.parse_atom_bytes(resp.content)
    assert feed.title.value == 'my title'

  1. ...When is it a good practice to do this vs. using selenium?

Short answer - you don't need it. I havent't paid much attention when reading your question and had HTML pages in mind when writing the comment. Regarding this selenium remark - this library handles all the low-level stuff, so when the tests start to accumulate in count (and usually, they do pretty fast), writing

uri = reverse('news-feed')
resp = client.get(uri)
root = parser.parse(resp.content)
assert root.query('some-query')

and dragging the imports along becomes too much hassle, so selenium can replace it with

driver = WebDriver()
driver.get(uri)
assert driver.find_element_by_id('my-element').text == 'my value'

Sure, testing with an automated browser instance has other advantages like seeing exactly what the user would see in real browser, allowing the pages to execute client-side javascript etc. But of course, all of this applies mainly to HTML pages testing; in case of testing against the RSS feed selenium usage is an overkill and Django's testing tools are more than enough.

Abut answered 10/6, 2019 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.