Skip to content

Instantly share code, notes, and snippets.

@wolever
Last active June 26, 2024 07:06
Show Gist options
  • Save wolever/c016d95e25d738fd57dc to your computer and use it in GitHub Desktop.
Save wolever/c016d95e25d738fd57dc to your computer and use it in GitHub Desktop.
Django testing things. To be put in a `testing` subdirectory of the project root.
from .testcase import MyAppTestCase, Client
from nose.tools import assert_equal
from myapp.testing import MyAppTestCase, factories as f
class TestGetUserProfile(MyAppTestCase):
class SETTINGS:
MY_SETTING = True
def test_get_user_profile(self):
profile = f.UserProfileFactory(
user__email="test@example.com",
)
self.client.login(profile.user)
resp = self.client.get("/api/profile", assertOk=True)
assert resp.json
assert_equal(resp.json.get("email"), profile.user.email)
  1. Put these files in yourapp/testing/
  2. Install:
  3. Run tests with python manage.py test, or test specific files or directories with python manage.py test myapp/tests/test_models.py
  1. Test cases which use the database must subclass MyAppTestCase, but other test can use test functions, generators, and everything else supported by nose: https://nose.readthedocs.org/en/latest/writing_tests.html
  2. Suggested: use nose-parameterized: https://github.com/wolever/nose-parameterized
from django.contrib.auth.models import User
class TestcaseUserBackend(object):
""" Allows users to be logged in with `login(testcase_user=user)`. """
def authenticate(self, testcase_user=None):
return testcase_user
def get_user(self, user_id):
return User.objects.get(pk=user_id)
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User
from .utils import StringGenerator, Factory as BaseFactory
gen = StringGenerator()
Factory = BaseFactory.for_gen(gen)
# Note: unicode strings are used where ever possible to shake out unicode bugs
UserFactory = Factory(User,
username=gen(u"üser-{num}"),
first_name=gen(u"Ålex-{num}"),
last_name=gen(u"Smi†h-{num}"),
email=gen("user-{num}@example.com"),
# password is 'asdf'
password="sha1$zxf4jarkOqYz$848025de709e9f6b5762add9b1dd1ea986d2b361",
)
# ... add factories for your models here.
# Model_mommy will automatically populate fields with reasonable defaults
# ProfileFactory = Factory(UserProfile,
# user=UserFactory,
# )
import json
import urllib
import urlparse
from django.conf import settings
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.test.client import Client as DjangoClient
from . import factories as f
class MyAppTestCase(DjangoTestCase):
class SETTINGS_BASE:
UNDER_TEST = True
AUTHENTICATION_BACKENDS = [
"akindi.testing.auth_backends.TestcaseUserBackend",
] + settings.AUTHENTICATION_BACKENDS
class SETTINGS:
# Suclassess can define a SETTINGS attribute with custom settings
pass
def __init__(self, *args, **kwargs):
self.init_helpers()
super(AkindiTestCase, self).__init__(*args, **kwargs)
self.client = Client(self)
def setUp(self):
super(AkindiTestCase, self).setUp()
self._old_settings = dict(settings.__dict__)
settings_overrides = (
self.SETTINGS_BASE.__dict__.items() +
self.get_helper_settings().items() +
self.SETTINGS.__dict__.items()
)
for name, val in settings_overrides:
setattr(settings, name, val)
self.setup_()
def tearDown(self):
super(AkindiTestCase, self).setUp()
for name, val in self._old_settings.items():
setattr(settings, name, val)
self.teardown_()
def teardown_helpers(self):
for helper in reversed(self._helpers):
helper.teardown()
def teardown_(self):
# Subclasses can override this without calling super()
pass
def setup_(self):
# Subclasses can override this without calling super()
pass
#
# Utility methods
#
def assertRespOk(self, resp):
self.assertGreaterEqual(resp.status_code, 200)
if resp.status_code >= 300:
if resp.status_code <= 399:
raise AssertionError("%s is not <= 299 (location: %s)"
%(resp.status_code, resp.get("location")))
raise AssertionError("%s is not <= 300 (content: %r)" %(
resp.status_code, resp.content,
))
def assertRedirect(self, resp, location):
assert resp.status_code in [301, 302], \
"Bad status code: %r (not a redirect; content: %r)" %(
resp.status_code, resp.content
)
actual_location = resp.get("location")
assert actual_location, "No Location header"
if location.startswith("/"):
split = urlparse.urlsplit(resp["location"])
actual_location = urlparse.urlunsplit(('', '') + split[2:])
self.assertEqual(location, actual_location)
def reverse(self, *args, **kwargs):
get = kwargs.pop("get", None)
url = reverse(*args, **kwargs)
if get:
url += "?" + urllib.urlencode(get)
return url
def assertRespNotOk(self, resp, content=None, status=None):
if resp.content == "DAJAXICE_EXCEPTION":
return
if status is not None:
self.assertEqual(resp.status_code, status, 'status %r != %r (content: %r)' %(
resp.status_code, status, resp.content,
))
elif resp.status_code <= 299:
raise AssertionError('unexpected good status code: %r (content: %r)'
%(resp.status_code, resp.content))
if content is not None:
self.assertIn(content, resp.content)
class Client(DjangoClient):
def __init__(self, testcase):
self.testcase = testcase
super(Client, self).__init__()
def set_options(self, options):
self.defaults.update(options)
def login(self, user=None):
user = user or f.UserFactory()
self.user = user
res = super(Client, self).login(testcase_user=user)
if not res:
raise AssertionError("Could not login as %r" %(user, ))
def _verb_handler(self, verb, *args, **kwargs):
assertOk = kwargs.pop("assertOk", False)
resp = getattr(DjangoClient, verb)(self, *args, **kwargs)
if assertOk:
self.testcase.assertRespOk(resp)
if "json" in resp.get("content-type", ""):
resp.json = json.loads(resp.content)
else:
resp.json = None
return resp
def _verb_wrapper(verb):
def _verb_wrapper_helper(self, *args, **kwargs):
return self._verb_handler(verb, *args, **kwargs)
_verb_wrapper_helper.__name__ = verb
return _verb_wrapper_helper
get = _verb_wrapper("get")
post = _verb_wrapper("post")
head = _verb_wrapper("head")
put = _verb_wrapper("put")
options = _verb_wrapper("options")
delete = _verb_wrapper("delete")
from functools import partial
from model_mommy import mommy
class StringGenerator(object):
def __init__(self):
self.stack = []
self.num = 0
self.next = 1
self.need_incr = True
self.need_decr = False
def incr(self):
self.need_incr = True
def decr(self):
if self.need_decr:
self.num = self.stack.pop()
self.need_decr = False
def _generator(self, s):
if self.need_incr:
self.need_incr = False
self.stack.append(self.num)
self.need_decr = True
self.num = self.next
self.next += 1
return unicode(s).format(num=self.num)
def __call__(self, s):
return partial(self._generator, s)
class Factory(object):
def __init__(self, gen, model, **attrs):
self.gen = gen
self.model = model
self.attrs = attrs
@classmethod
def for_gen(cls, gen):
return partial(cls, gen)
def _prep_attrs(self, attrs):
for key, val in attrs.items():
if callable(val):
attrs[key] = val()
attrs = self.prep_attrs(attrs)
return attrs
def prep_attrs(self, attrs):
# Subclasses can override this
return attrs
def build_attrs(self, custom_attrs):
attrs = dict(self.attrs)
attrs.update(custom_attrs)
self.gen.incr()
attrs = self._prep_attrs(attrs)
self.gen.decr()
return attrs
def __call__(self, **custom_attrs):
return mommy.make(self.model, **self.build_attrs(custom_attrs))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment