Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 442
zerohedge Avatar asked Jun 08 '19 23:06

zerohedge


People also ask

Can I use pytest with Django?

Setting Up pytest for a Django ProjectThe pytest-django plugin is maintained by the pytest development team. It provides useful tools for writing tests for Django projects using pytest . This is the minimum amount of configuration needed to make pytest work with your Django project.

Does Django use pytest or Unittest?

Writing testsDjango's unit tests use a Python standard library module: unittest . This module defines tests using a class-based approach.

How do I skip Testcases in pytest?

Skipping a test The simplest way to skip a test is to mark it with the skip decorator which may be passed an optional reason . It is also possible to skip imperatively during test execution or setup by calling the pytest. skip(reason) function.


1 Answers

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.

like image 191
hoefling Avatar answered Oct 11 '22 10:10

hoefling