Tests für View schreiben - User login scheitert

Django, Flask, Bottle, WSGI, CGI…
Antworten
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Hi, ich möchte ab nun Tests für meine Views schreiben. Meine Views sind eig. alle durch CustomPermissions (abgeleitet von rest_framework.permissions.BasePermission) entsprechend geschützt.
Der Login erfolgt über Token. Ein Test könnte so aussehen:

Code: Alles auswählen

class OrganizationCreateTests(TestCase):
    def setUp(self):
        password = "password123!"
        email = "testmail@test.de"
        self.user = get_user_model().objects.create_user(email=email, password=password)

    def test_create(self):
        c = Client()
        c.force_login(self.user)
        response = c.post(reverse(OrganizationCreateView.__name__),{'Test_attr':'Test'})
c.force_login gibt None zurück.

Ich habe auch schon versucht (da ich es ja so auch in Postman machen würde), den Token beim request mitzugeben, anstatt einen login zu forcieren (so erfolgt es ja in 'echt'):

Code: Alles auswählen

        header = {'Authorization':f'Token {self.token}'}
        response = self.client.post(reverse(OrganizationCreateView.__name__), header)
Klappt leider auch nicht.

Die Fehlermeldung, wenn ich per

Code: Alles auswählen

py manage.py test
Tests ausführe, lautet:
Traceback (most recent call last):
File "C:\Users\Robin\dev\rechnuwo-projekt\rechnuwo_proj\rechnuwo\tests.py", line 123, in test_create2
response = c.post(reverse(OrganizationCreateView.__name__),{'organization_name':'TestOrgaName'})
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\test\client.py", line 751, in post
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\test\client.py", line 407, in post
return self.generic('POST', path, post_data, content_type,
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\test\client.py", line 473, in generic
return self.request(**r)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\test\client.py", line 719, in request
self.check_exception(response)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\test\client.py", line 580, in check_exception
raise exc_value
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\django\views\generic\base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 497, in dispatch
self.initial(request, *args, **kwargs)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 415, in initial self.check_permissions(request)
File "C:\Users\Robin\dev\rechnuwo-projekt\env-backend\lib\site-packages\rest_framework\views.py", line 332, in check_permissions
if not permission.has_permission(request, self):
File "C:\Users\Robin\dev\rechnuwo-projekt\rechnuwo_proj\rechnuwo\permissions.py", line 21, in has_permission
return request.user.organization is None
AttributeError: 'AnonymousUser' object has no attribute 'organization'
Es hängt also in meiner Permission fest, und zwar, weil ich nicht Authorisiert bin und dadurch der Request durch den AnonymousUser ausgeführt wird, der mein Attribut eben nicht kennt.

Ich habe schon einige Stunden verbracht und alles mögliche ausprobiert, bin aber leider nicht weitergekommen.
Noch relevant zu wissen: Ich habe ein CustomUserModel:

Code: Alles auswählen

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    organization = models.ForeignKey(Organization, on_delete=models.SET_NULL, related_name="staff", null=True, blank=True)
    isOrganizationAdmin = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email
Vielen Dank schonmal, ich hoffe ich hab alles notwendige reingepackt.
Benutzeravatar
sparrow
User
Beiträge: 4144
Registriert: Freitag 17. April 2009, 10:28

Schau mal in die Tests des rest frameworks. Dort gibt es Beispiele inkl. Klasse von der sie erben. Darauf baust du auf.
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Danke schonmal für die Antwort, bin einen Schritt weiter.

Eine Frage, die ich bisher nicht klären:

Code: Alles auswählen

class OrganizationCreateTests(APITestCase):

    def setUp(self):
        self.factory = APIRequestFactory()
        self.view = OrganizationCreateView.as_view()


    def test_something(self):

        user = get_user_model().objects.create_user(email='TestUser@mail.com', password="TestPass")
        request = self.factory.post(
            reverse(OrganizationCreateView.__name__), 
            {"organization_name":"TestName"},
            format="json")
        force_authenticate(request, user=user)
        response = self.view(request)
        self.assertEqual(response.status_code,status.HTTP_201_CREATED)
Hierbei wird jetzt auch die jeweilige Permission der View berücksichtigt, auch wenn durch force_authenticate() berücksichtigt wird? In der Doku hab ich nichts gefunden was Permissions und APITestCase bzw. APIRequestFactory angeht.. Danke!

P.S.: Was mir noch aufgefallen ist:

Wenn ich einen einen user in setUp() mit self.user = ... anlege, ist er über alle Tests (innerhalb der Class) verfügbar. Wenn ich ihn in einem Test erstelle, ist er im nächsten Test derselben Klasse nicht mehr verfügbar.
Benutzeravatar
sparrow
User
Beiträge: 4144
Registriert: Freitag 17. April 2009, 10:28

Ich benutze die Tests so:

Code: Alles auswählen

from rest_framework.test import APITestCase

class ARandomTestClass(APITestCase):

    def setUp(self):
        self.user = [....]

    def test_an_endpoitns(self):
        self.client.force_authenticate(self.user)
        url = reverse(
            'the_url_name', 
            kwargs={'an_url_parameter_name': an_url_parameter_value}
        )
        response = self.client.get(url)
Und zum PS:
Warum wundert dich das? Nach jedem Test (und jede Funktion in der Klasse ist ein Test) wird wieder der Zustand nach setUp hergestellt. Deshalb legt man alles, was man für alle Tests braucht, dort an. Alles was du in den Tests selbst tust ist nach diesem einen speziellen Test natürlich weg.
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Jo danke.
Ich habe die Doku falsch verstanden, ich dachte, dass quasi jede Klasse und nicht jeder Test auf den Ursprungszustand gebracht wird
robin_
User
Beiträge: 48
Registriert: Montag 3. August 2020, 17:59

Hi, ich hätte nochmal eine Frage - dieses mal in Richtung 'best practice'.

Ist eine solche Implementierung:

Code: Alles auswählen

def test_get(self):
        PROJ_NAME = "Proj Description"
        slug_index = 1

        test_cases = [
            (create_test_user_without_organization, True, status.HTTP_403_FORBIDDEN),
            (create_test_user_without_organization, False, status.HTTP_403_FORBIDDEN),
            (create_test_user_with_organization, False, status.HTTP_200_OK),
            (create_test_user_with_organization, True, status.HTTP_200_OK),
        ]

        for user_create_func, isAdmin, status_code in test_cases:
            url = reverse(ProjectRetrieveUpdateDestroyView.__name__, kwargs={'pk': slug_index})
            ...

Sinnvoll oder eher nicht? Also perspektivisch, wenn man vielleicht auch nochmal eine View überarbeitet, wäre das oben einen tick schlechter zu debuggen. Auf der anderen Seite spart man sich wiederholenden Code und auch Tests an sich...

Würdet ihr mir davon abraten?
Danke!
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@robin_: Das ``...`` ist da IMHO eine interessante Stelle. Das sollte nicht *ein* Test sein, aber `unittest` bietet da wohl mittlerweile auch was für parametrisierte Tests.

Ich mag ja `pytest` sehr gerne…
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten