Unit Testing with ``paste.fixture``
++++++++++++++++++++++++++++++++++++

Pylons provides powerful unit testing capabilities for your web application 
utilizing `paste.fixture <http://pythonpaste.org/testing-applications.html#the-tests-themselves>`_
to emulate requests to your web application. You can then ensure that the 
response was handled appropriately and that the controller set things up 
properly.

To run the test suite for your web application, Pylons utilizes the `nose 
<http://somethingaboutorange.com/mrl/projects/nose/>`_ test runner/discovery 
package. Running ``nosetests`` in your project directory will run all the 
tests you create in the tests directory. If you don't have nose installed on
your system, it can be installed via setuptools with::

    easy_install -U nose

.. note::
    ``paste.fixture`` is currently incompatible with Windows, we are working 
    on remedying this.

Example: Testing a Controller
=============================

First let's create a new project and controller for this example::

    paster create --template=pylons TestExample
    cd TestExample
    paster controller comments

You'll see that it creates two files when you create a controller. The stub controller, and a test for it under ``testexample/tests/functional/``. 

Modify the ``testexample/controllers/comments.py`` file so it looks like this:

.. code-block:: Python

    from testexample.lib.base import *

    class CommentsController(BaseController):
        def index(self):
            return Response('Basic output')

        def sess(self):
            session['name'] = 'Joe Smith'
            session.save()
            return Response('Saved a session')

Then write a basic set of tests to ensure that the controller actions are functioning properly, modify ``testexample/tests/functions/test_comments.py`` to match the following:

.. code-block:: Python

    from testexample.tests import *

    class TestCommentsController(TestController):
        def test_index(self):
            response = self.app.get(url_for(controller='/comments'))
            assert 'Basic output' in response

        def test_sess(self):
            response = self.app.get(url_for(controller='/comments', action='sess'))
            assert response.session['name'] == 'Joe Smith'
            assert 'Saved a session' in response

Run ``nosetests`` in your main project directory and you should see them all pass::

    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 2.999s

    OK

Unfortunately, nosetests (as of 0.8.6 at least) cannot provide detailed information about the results of an assertion should it fail when you use this format for testing. For example, add the following test to the ``test_sess`` function:

.. code-block:: Python

    assert response.session.has_key('address') == True

When you run ``nosetests`` you will get the following, not-very-helpful response::

    .F
    ======================================================================
    FAIL: test_sess (testexample.tests.functional.test_comments.TestCommentsController)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "~/TestExample/testexample/tests/functional/test_comments.py", line 12, in test_sess
        assert response.session.has_key('address') == True
    AssertionError: 


    ----------------------------------------------------------------------
    Ran 2 tests in 1.417s

    FAILED (failures=1)

Since our TestController we inherited from is a standard Python unit testing controller we can use the assertEqual statement which will provide more information. The new test line looks like this:

.. code-block:: Python

    self.assertEqual(response.session.has_key('address'), True)

Which provides the more useful failure message::

    .F
    ======================================================================
    FAIL: test_sess (testexample.tests.functional.test_comments.TestCommentsController)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "~/TestExample/testexample/tests/functional/test_comments.py", line 12, in test_sess
        self.assertEqual(response.session.has_key('address'), True)
    AssertionError: False != True


Testing Pylons Objects
======================

Pylons will provide several additional attributes for the ``paste.fixture`` response object that let you access various objects that were created during the web request:

``session``
    Session object
``req``
    Request object    
``c``
    Object containing variables passed to templates
``g``
    Globals object

To use them, merely access the attributes of the response *after* you've used
a get/post command::

    response = app.get('/some/url')
    assert response.session['var'] == 4
    assert 'REQUEST_METHOD' in response.req.environ

.. note::
    The ``paste.fixture`` response object already has a TestRequest object 
    assigned to it, therefore Pylons assigns its ``request`` object to the 
    response as ``req``.

Testing Your Own Objects
========================

Paste's fixture testing allows you to designate your own objects that you'd 
like to access in your tests. This powerful functionality makes it easy to 
test the value of objects that are normally only retained for the duration of
a single request.

Before making objects available for testing, its useful to know when your 
application is being tested. Paste will provide an environ variable called
``paste.testing`` that you can test for the presence and truth of so that your
application only populates the testing objects when it has to.

Populating the ``paste.fixture`` response object with your objects is done by
adding them to the environ dict under the key ``paste.testing_variables``. 
Pylons creates this dict before calling your application, so testing for its
existence and adding new values to it is recommended. All variables assigned
to the ``paste.testing_variables`` dict will be available on the response 
object with the key being the attribute name.

Example:

.. code-block:: Python
    
    # testexample/lib/base.py
    
    from pylons import Response, c, g, cache, request, session
    from pylons.controllers import WSGIController
    from pylons.decorators import jsonify, rest, validate
    from pylons.templating import render, render_response
    from pylons.helpers import abort, redirect_to, etag_cache
    import testexample.models as model
    import testexample.helpers as h

    class BaseController(WSGIController):
        def __call__(self, environ, start_response):
            # Create a custom email object
            email = MyCustomEmailObj()
            email.name = 'Fred Smith'
            if 'paste.testing_variables' in request.environ:
                request.environ['paste.testing_variables']['email'] = email
            return WSGIController.__call__(self, environ, start_response)
    
    
    # testexample/tests/functional/test_controller.py
    from testexample.tests import *

    class TestCommentsController(TestController):
        def test_index(self):
            response = self.app.get(url_for(controller='/'))
            assert response.email.name == 'Fred Smith'
    

More Details
============
    
For more details on running tests using get/post, and testing the response, headers, etc., see the Paste document on web application testing:

`Testing Application with Paste, the Tests <http://pythonpaste.org/testing-applications.html#the-tests-themselves>`_
