Tester une vue Django

2016-05-12
Thomas Martin


A l'occasion du développement d'une application web en Python (j'en reparlerai peut-être plus tard), j'ai eu le plaisir de me replonger dans le framework Django. Je découvre au passage l'existence du client de test HTTP, qui permet d'interagir directement avec une vue dans la suite de tests unitaires.

Un exemple concret : suite à une grosse bévue de ma part (heureusement détectée très rapidement avant push), il était possible pour n'importe quel utilisateur authentifié de consulter l'ensemble des éléments d'un modèle, y compris ceux qui n'étaient pas liés au user_id de l'utilisateur en question. L'erreur fût aisément corrigée, mais il me fallait un test pour m'assurer que cela ne se reproduise plus. Et pour cela je devais interagir directement avec la vue en question.

Au passage, Django est tellement puissant et simplifie tellement les choses (vues génériques, découverte automatique des templates, etc), qu'on en arrive à moins réfléchir, et à considérer inconsciemment que celui-ci prendra en charge automatiquement certains aspects du développement (dans ce cas précis, filtrer les élements d'un modèle disposant d'une ForeignKey vers une classe User). Ce cas fût un bon rappel à l'ordre pour moi.

L'exemple en question extrait du fichier tests.py de l'application :

from django.contrib.auth.models import User
from .models import Document

[...]

def test_document_detail_non_readable_by_non_owner(self):
    """Ensure it's not possible for an user to access document owned by others."""

On crée deux utilisateurs de test (pour rappel les tests unitaires se jouent sur une base de données vide créée pour l'occasion) :

user_bob = User.objects.create_user('bob')
user_bob.save()
user_roger = User.objects.create_user('roger')
user_roger.save()

On crée un Document (objet métier de l'application) possédé par bob :

doc = Document(user=user_bob)
doc.save()

On s'assure que roger recevra une erreur HTTP 404 s'il tente d'accèder à la ressource. A noter l'appel de force_login() qui permet de ne pas avoir à s'embarrasser de mots de passe dans le test, et de reverse() pour ne pas coder en dur l'URL de la ressource :

self.client.force_login(user_roger)
res = self.client.get(reverse('document-detail', args=[doc.pk]))
self.assertEqual(res.status_code, 404)