Test tools

Enforcing multi-factor authentication creates some challenges in (integration) tests for the admin, either with django’s test django.test.Client or with other solutions like django-webtest.

We have some guidelines and tools to make dealing with those difficulties much more easy.

Built in helpers

Disabling MFA checks

You can use the maykin_2fa.test.disable_mfa() decorator/context manager to automatically mark an authenticated user in the admin as verified. This uses the MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS setting under the hood.

It includes the authentication backends used by django-webtest, so you can use it without any extra steps on WebTest-based test cases.

Example:

from django_webtest import WebTest
from maykin_2fa.test import disable_mfa


@disable_mfa()
class MyAdminTests(WebTest):

    def test_admin_index(self):
        user = User.objects.create_user(
            username="admin", password="password", is_staff=True
        )

        response = self.app.get(reverse("amdin:index"), user=user)

        self.assertEqual(response.status_code, 200)

TOTP token

When a TOTP token device is set up for a user, you can easily generate a valid authentication token using maykin_2fa.test.get_valid_totp_token().

See Factory-boy for a code sample.

Recipes

Factory-boy

Factory-boy is a popular solution to generate realistic dummy data. You can leverage it to also automatically set up a TOTP device to reflect a token generator second factor by using the Trait feature from factory boy.

accounts/tests/factories.py
 # e.g. in accounts/tests/factories.py
 import factory
 from django_otp.util import random_hex


 class TOTPDeviceFactory(factory.django.DjangoModelFactory):
     user = factory.SubFactory("accounts.tests.factories.UserFactory")
     key = factory.LazyAttribute(lambda o: random_hex())

     class Meta:
         model = "otp_totp.TOTPDevice"


 class UserFactory(factory.django.DjangoModelFactory):
     username = factory.Sequence(lambda n: f"user-{n}")
     password = factory.PostGenerationMethodCall("set_password", "secret")

     class Meta:
         model = "accounts.User"

     class Params:
         with_totp_device = factory.Trait(
             device=factory.RelatedFactory(
                 TOTPDeviceFactory,
                 "user",
                 name="default",
             )
         )

And then you can use it as:

tests/test_something.py
 from maykin_2fa.test ipmort get_valid_totp_token
 from two_factor.utils import default_device, totp_digits

 from accounts.tests.factories import UserFactory

 user = UserFactory.create(with_totp_device=True)
 token = get_valid_totp_token(user)
 assert isinstance(token, str)