Four Tips on Identity Testing

I remember that day about a month ago, when it occured to me it is high time that I write some unit tests for my project. In the beginning, it felt like travelling where no man has gone before – a lot of technical issues to settle. I introduced my BrowserSession class to aid stateful unit testing few days ago. From responses that I got, it seems that a lot of people are still fighting with the technicalities of setting up the testing environment. So I took the time to give some hints and tips on the matter. Attached to this post is a project which demonstrates simple identity tests (that work).

Before you start, it will be helpful (but absolutely not required) if you also read about the BrowserSession object that is used in this post.

Tip 1: Set up the test configuration right.

None of your configuration files (dev.cfg, prod.cfg or app.cfg) is not loaded when you run your tests. Therefore, at the beginning of test_controllers.py, set your configuration options:

turbogears.config.update({
    'visit.on': True, 
    'identity.on': True,
    'identity.failure_url': '/login',
    })

Another possibility is to create a configuration environment for testing, test.cfg and manually load it, using something like:

turbogears.update_config(configfile='project_dir/test.cfg',  
                         modulename='package_name.config')

Tip 2: Initialize your database right.

testutil will set the default database to in-memory sqlite database. This looks like a good choice to me (you want something that will not be affected by previous runs and is fast). If you want to change that, make sure you do it after you import testutil. I never tried this.
In any case, your tables may not exist, and it is a good time to create them once the test module is imported. You can also use this opportunity to fill the tables with some data you want to use in your tests later:

def init_database():
    # hack follows: to create a TG_User, a request
    # environment must be around...
    testutil.create_request('/')
    if TG_User.selectBy(user_name='thesamet').count() == 0:
        TG_User(user_name='thesamet', password='password', 
                display_name='Nadav', email_address='spam@me.not')

    Table.create(ifNotExists=True)
    Table.create(table_number=3, table_location='far away')

Note the comment at the beginning of the function. Unless there is a cherrypy request around, you can’t create a TG_User record. This is due to some unfortunate coupling.

Tip 3: Identity likes your login buttons.

When you emulate a user filling the login form, don’t forget to pass also the login button value (which is ‘Login’). If you login through the /login url (as opposed to give a url of some identity-protected method), do not forget to pass the forward_url argument. Example:

user = BrowserSession()
user.goto('/login?user_name=thesamet&password=password' \\
          '&login=Login&forward_url=/')
assert user.status != 403     # not forbidden

Tip 4: Never forget to call stopTurboGears()

You must call turbogears.startup.stopTurboGears() at the end of each test. I won’t pretend that I understand why it is needed. It is also done in TurboGears’ own identity tests. If you don’t do that you’ll get nice exceptions from the VisitManager thread. I like to factor out the stopTurboGears() call to the tearDown part of my test case.

The following is an example of a test case objects that verifies that the protected resource /secret is well-protected:

class SecretPageTest(unittest.TestCase):
    def setUp(self):
        self.user = BrowserSession()

    def test_anonymous(self):
        self.user.goto('/')
        assert 'Login' in self.user.response
        self.user.goto('/secret')
        assert 'You must provide your credentials' \\
                  in self.user.response

    def test_bad_credentials(self):
        self.user.goto('/secret?user_name=thesamet&password=incorrect'\\
                       '&login=Login')
        assert 'The credentials you supplied were not correct' \\
                           in self.user.response

    def test_successful_login(self):
        self.user.goto('/secret?user_name=thesamet&password=password' \\
                       '&login=Login')
        assert 'This is a secret page' in self.user.response

    def test_logout(self):
        self.user.goto('/login?user_name=thesamet&password=password' \\
                       '&login=Login&forward_url=/')
        # not forbidden
        assert self.user.status != 403 
        self.user.goto('/')
        # display name should appear and no suggestion to login
        assert 'Nadav' in self.user.response
        assert '<A HREF="/login">Login</A>' not in self.user.response
        self.user.goto('/logout')
        self.user.goto('/')
        # no display name should appear and a suggestion to login
        assert 'Nadav' not in self.user.response
        assert '<A HREF="/login">Login</A>' in self.user.response

    def tearDown(self):
        turbogears.startup.stopTurboGears()

You can download the full source code of a minimal identity testable project.

This entry was posted in howto, turbogears. Bookmark the permalink.

51 Responses to Four Tips on Identity Testing

Leave a Reply

Your email address will not be published. Required fields are marked *