Commits

Mikhail Korobov committed 95d8eb6

Initial commit

Comments (0)

Files changed (6)

+syntax: glob
+
+#IDE files
+.settings/*
+.project
+.pydevproject
+.cache/*
+
+#temp files
+*.pyc
+*.pyo
+*.orig
+*~
+
+#misc files
+pip-log.txt
+
+#os files
+.DS_Store
+Thumbs.db
+
+#setup files
+build/
+dist/
+.build/
+MANIFEST
+Copyright (c) 2010 Mikhail Korobov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+include *.txt
+include *.rst
+recursive-include docs *.txt
+==============
+django-webtest
+==============
+
+django-webtest is an almost trivial app for instant integration of
+Ian Bicking's WebTest (http://pythonpaste.org/webtest/) with django's
+testing framework.
+
+
+Installation
+============
+
+::
+    $ pip install webtest
+    $ pip install django-webtest
+
+or ::
+
+    $ easy_install webtest
+    $ easy_install django-webtest
+
+or grab latest versions from bitbucket
+(http://bitbucket.org/ianb/webtest, http://bitbucket.org/kmike/django-webtest)
+
+Usage
+=====
+
+django-webtest provides django.test.TestCase subclass (``WebTest``) that creates
+``webtest.TestApp`` around django wsgi interface and make it available in
+tests as ``self.app``.
+
+It also features optional ``user`` argument for ``self.app.get`` and
+``self.app.post`` methods to help making authorized requests. This argument
+should be django.contrib.auth.models.User's ``username`` for user who is
+supposed to be logged in.
+
+All of these features can be easily set up manually (thanks to WebTest
+architecture) and they are even not neccessary for using WebTest with django but
+it is nice to have some sort of integration instantly.
+
+::
+
+    from django_webtest import WebTest
+
+    class MyTestCase(WebTest):
+
+        # we want some initial data to be able to login
+        fixtures = ['users', 'blog_posts']
+
+        def testBlog(self):
+            # pretend to be logged in as user `kmike` and go to the index page
+            index = self.app.get('/', user='kmike')
+
+            # All the webtest API is available. For example, we click
+            # on a <a href='/tech-blog/'>Blog</a> link, check that it
+            # works (result page doesn't raise exceptions and returns 200 http
+            # code) and test if result page have 'My Article' text in
+            # it's body.
+            assert 'My Article' in index.click('Blog')
+
+See http://pythonpaste.org/webtest/ for API help. It can follow links, submit
+forms, parse html, xml and json responses with different parsing libraries,
+upload files and more.
+
+Why?
+====
+
+While django.test.client.Client is fine for it's purposes, it is not
+well-suited for functional or integration testing. From django's test client
+docstring:
+
+    This is not intended as a replacement for Twill/Selenium or
+    the like - it is here to allow testing against the
+    contexts and templates produced by a view, rather than the
+    HTML rendered to the end-user.
+
+WebTest plays on the same field as twill. WebTest has nice API, is fast, small,
+talk to django application via WSGI instead of HTTP and is an easy way to
+write functional/integration/acceptance tests.
+
+Twill is also a great tool and it also can be easily integrated with django
+(see django-test-utils package) and I also enjoy it much. But I prefer WebTest
+over twill because twill is old (last release is in 2007), communicate via HTTP
+instead of WSGI (though there is workaround for that), lacks support for
+non-latin text (not to mention unicode) and have a much larger codebase to
+hack on. Twill however understands HTML better and is more mature so
+consider it (and django-test-utils package) if WebTest doesn't fit for some
+reason.

django_webtest/__init__.py

+#coding: utf-8
+from django.conf import settings
+from django.core.handlers.wsgi import WSGIHandler
+from django.core.servers.basehttp import AdminMediaHandler
+from django.db import close_connection
+from django.core import signals
+from django.test import TestCase
+from webtest import TestApp, TestRequest
+
+
+class DjangoWsgiFix(object):
+    """Django closes the database connection after every request;
+    this breaks the use of transactions in your tests. This wraps
+    around Django's WSGI interface and will disable the critical
+    signal handler for every request served.
+
+    Note that we really do need to do this individually a every
+    request, not just once when our WSGI hook is installed, since
+    Django's own test client does the same thing; it would reinstall
+    the signal handler if used in combination with us.
+
+    From django-test-utils.
+    """
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        signals.request_finished.disconnect(close_connection)
+        try:
+            return self.app(environ, start_response)
+        finally:
+            signals.request_finished.connect(close_connection)
+
+
+class DjangoTestApp(TestApp):
+
+    def __init__(self, extra_environ=None, relative_to=None):
+        app = DjangoWsgiFix(AdminMediaHandler(WSGIHandler()))
+        super(DjangoTestApp, self).__init__(app, extra_environ, relative_to)
+
+
+    def get(self, url, params=None, headers=None, extra_environ=None,
+            status=None, expect_errors=False, user=None):
+        if user:
+            extra_environ = extra_environ or {}
+            extra_environ['REMOTE_USER'] = user
+        return super(DjangoTestApp, self).get(url, params, headers, extra_environ,
+                                              status, expect_errors)
+
+
+    def post(self, url, params=None, headers=None, extra_environ=None,
+             status=None, upload_files=None, expect_errors=False,
+             content_type=None, user=None):
+        if user:
+            extra_environ = extra_environ or {}
+            extra_environ['REMOTE_USER'] = user
+        return super(DjangoTestApp, self).post(url, params, headers, extra_environ,
+                                               status, expect_errors, content_type)
+
+
+class WebTest(TestCase):
+
+    def _patch_settings(self):
+        ''' Patch settings to add support for REMOTE_USER authorization '''
+        self._MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES[:]
+        self._AUTHENTICATION_BACKENDS = settings.AUTHENTICATION_BACKENDS[:]
+
+        remote_user_middleware = 'django.contrib.auth.middleware.RemoteUserMiddleware'
+        if not remote_user_middleware in settings.MIDDLEWARE_CLASSES:
+            settings.MIDDLEWARE_CLASSES += (remote_user_middleware,)
+
+        auth_backends = list(settings.AUTHENTICATION_BACKENDS)
+        try:
+            index = auth_backends.index('django.contrib.auth.backends.ModelBackend')
+            auth_backends[index] = 'django.contrib.auth.backends.RemoteUserBackend'
+        except ValueError:
+            auth_backends.append('django.contrib.auth.backends.RemoteUserBackend')
+        settings.AUTHENTICATION_BACKENDS = auth_backends
+
+    def _unpatch_settings(self):
+        ''' Restore settings to before-patching state '''
+        settings.MIDDLEWARE_CLASSES = self._MIDDLEWARE_CLASSES
+        settings.AUTHENTICATION_BACKENDS = self._AUTHENTICATION_BACKENDS
+
+    def __call__(self, result=None):
+        self._patch_settings()
+        self.app = DjangoTestApp()
+        res = super(WebTest, self).__call__(result)
+        self._unpatch_settings()
+        return res
+#!/usr/bin/env python
+from distutils.core import setup
+
+version='1.0'
+
+setup(
+    name='django-webtest',
+    version=version,
+    author='Mikhail Korobov',
+    author_email='kmike84@gmail.com',
+
+    packages=['django_webtest'],
+
+    url='http://bitbucket.org/kmike/django-webtest/',
+    download_url = 'http://bitbucket.org/kmike/django-webtest/get/tip.zip',
+    license = 'MIT license',
+    description = """ Instant integration of Ian Bicking's WebTest
+(http://pythonpaste.org/webtest/) with django's testing framework.""",
+
+    long_description = open('README.rst').read(),
+    requires = ['WebTest'],
+
+    classifiers=(
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Framework :: Django',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: MIT License',
+        'Programming Language :: Python',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+    ),
+)