CKAN 2.2 documentation » Writing CKAN extensions »

Testing extensions

CKAN extensions can have their own tests that are run using nosetests in much the same way as running CKAN’s own tests (see Testing CKAN).

Continuing with our example_iauthfunctions extension, first we need a CKAN config file to be used when running our tests. Create the file ckanext-iauthfunctions/test.ini with the following contents:

[app:main]
use = config:../ckan/test-core.ini

The use line declares that this config file inherits the settings from the config file used to run CKAN’s own tests (../ckan should be the path to your CKAN source directory, relative to your test.ini file).

The test.ini file is a CKAN config file just like your /etc/ckan/default/development.ini and /etc/ckan/default/production.ini files, and it can contain any CKAN config file settings that you want CKAN to use when running your tests, for example:

[app:main]
use = config:../ckan/test-core.ini
ckan.site_title = My Test CKAN Site
ckan.site_description = A test site for testing my CKAN extension

Next, make the directory that will contain our test modules:

mkdir ckanext-iauthfunctions/ckanext/iauthfunctions/tests/

Finally, create the file ckanext-iauthfunctions/ckanext/iauthfunctions/tests/test_iauthfunctions.py with the following contents:

'''Tests for the ckanext.example_iauthfunctions extension.

'''
import paste.fixture
import pylons.test

import ckan.model as model
import ckan.tests as tests
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit


class TestExampleIAuthFunctionsPlugin(object):
    '''Tests for the ckanext.example_iauthfunctions.plugin module.

    '''
    @classmethod
    def setup_class(cls):
        '''Nose runs this method once to setup our test class.'''

        # Make the Paste TestApp that we'll use to simulate HTTP requests to
        # CKAN.
        cls.app = paste.fixture.TestApp(pylons.test.pylonsapp)

        # Test code should use CKAN's plugins.load() function to load plugins
        # to be tested.
        plugins.load('example_iauthfunctions')

    def setup(self):
        '''Nose runs this method before each test method in our test class.'''

        # Access CKAN's model directly (bad) to create a sysadmin user and save
        # it against self for all test methods to access.
        self.sysadmin = model.User(name='test_sysadmin', sysadmin=True)
        model.Session.add(self.sysadmin)
        model.Session.commit()
        model.Session.remove()

    def teardown(self):
        '''Nose runs this method after each test method in our test class.'''

        # Rebuild CKAN's database after each test method, so that each test
        # method runs with a clean slate.
        model.repo.rebuild_db()

    @classmethod
    def teardown_class(cls):
        '''Nose runs this method once after all the test methods in our class
        have been run.

        '''
        # We have to unload the plugin we loaded, so it doesn't affect any
        # tests that run after ours.
        plugins.unload('example_iauthfunctions')

    def _make_curators_group(self):
        '''This is a helper method for test methods to call when they want
        the 'curators' group to be created.

        '''
        # Create a user who will *not* be a member of the curators group.
        noncurator = tests.call_action_api(self.app, 'user_create',
                                           apikey=self.sysadmin.apikey,
                                           name='noncurator',
                                           email='email',
                                           password='password')

        # Create a user who will be a member of the curators group.
        curator = tests.call_action_api(self.app, 'user_create',
                                        apikey=self.sysadmin.apikey,
                                        name='curator',
                                        email='email',
                                        password='password')

        # Create the curators group, with the 'curator' user as a member.
        users = [{'name': curator['name'], 'capacity': 'member'}]
        curators_group = tests.call_action_api(self.app, 'group_create',
                                               apikey=self.sysadmin.apikey,
                                               name='curators',
                                               users=users)

        return (noncurator, curator, curators_group)

    def test_group_create_with_no_curators_group(self):
        '''Test that group_create doesn't crash when there's no curators group.

        '''
        # Make sure there's no curators group.
        assert 'curators' not in tests.call_action_api(self.app, 'group_list')

        # Make our sysadmin user create a group. CKAN should not crash.
        tests.call_action_api(self.app, 'group_create', name='test-group',
                              apikey=self.sysadmin.apikey)

    def test_group_create_with_visitor(self):
        '''A visitor (not logged in) should not be able to create a group.

        Note: this also tests that the group_create auth function doesn't
        crash when the user isn't logged in.

        '''
        noncurator, curator, curators_group = self._make_curators_group()
        result = tests.call_action_api(self.app, 'group_create',
                                       name='this_group_should_not_be_created',
                                       status=403)
        assert result['__type'] == 'Authorization Error'

    def test_group_create_with_non_curator(self):
        '''A user who isn't a member of the curators group should not be able
        to create a group.

        '''
        noncurator, curator, curators_group = self._make_curators_group()
        result = tests.call_action_api(self.app, 'group_create',
                                       name='this_group_should_not_be_created',
                                       apikey=noncurator['apikey'],
                                       status=403)
        assert result['__type'] == 'Authorization Error'

    def test_group_create_with_curator(self):
        '''A member of the curators group should be able to create a group.

        '''
        noncurator, curator, curators_group = self._make_curators_group()
        name = 'my-new-group'
        result = tests.call_action_api(self.app, 'group_create',
                                       name=name,
                                       apikey=curator['apikey'])
        assert result['name'] == name

To run these extension tests, cd into the ckanext-iauthfunctions directory and run this command:

nosetests --ckan --with-pylons=test.ini ckanext/iauthfunctions/tests

Some notes on how these tests work:

  • Nose has lots of useful functions for testing, see the nose documentation.
  • We’re using a paste.fixture.TestApp object to simulate sending HTTP requests to the CKAN API or frontend. See Testing Applications with Paste for some documentation of this.
  • We’re calling ckan.tests.call_action_api() to post (simulated) HTTP requests to the CKAN API. This is a convenience function that CKAN provides for its own tests.
  • You might also find it useful to read the Pylons testing documentation.
  • The Pylons book also has a chapter on testing.

Todo

Link to CKAN guidelines for how to write tests, once those guidelines have been written. Also add any more extension-specific testing details here.