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)
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.
Should I assert that dummy_object
is in the response this way?
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.
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.
Writing testsDjango's unit tests use a Python standard library module: unittest . This module defines tests using a class-based approach.
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.
Reevaluating my comment (didn't carefully read the question and overlooked the RSS feed stuff):
- Is
reverse
-ing and then passing that into theclient.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.
- 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'
- ...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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With