Testing API

Last Updated: December 2022

Manually testing your app can be very time consuming, especially when modifying a simple line of code usually warrants retesting everything. To help automate and streamline the testing of your app, Tethys Platform provides you with a great starting point by providing the following:

  1. A tests directory with a tests.py script within your app's default scaffold that contains well-commented sample testing code.

  2. The Testing API which provides a helpful test class for setting up your app's testing environment.

Writing Tests

Tests should be written in a separate python script that is contained somewhere within your app's scaffold. By default, a tests directory already exists in the app-level directory and contains a tests.py script. Unless you have a good reason not to, it would be best to start writing your test code here.

As an example, if wanting to automate the testing of a the map controller in the "My First App" from the tutorials, the tests.py script might be modified to look like the following:

python
from tethys_sdk.testing import TethysTestCase
from ..app import MyFirstApp

class MapControllerTestCase(TethysTestCase):
    def set_up(self):
        self.create_test_persistent_stores_for_app(MyFirstApp)
        self.create_test_user(username="joe", email="joe@some_site.com", password="secret")
        self.client = self.get_test_client()

    def tear_down(self):
        self.destroy_test_persistent_stores_for_app(MyFirstApp)

    def test_success_and_context(self):
        self.client.force_login(self.user)
        response = self.client.get('/apps/my-first-app/map/')

        # Check that the response returned successfully
        self.assertEqual(response.status_code, 200)

        # Check that the response returned the context variable
        self.assertIsNotNone(response.context['map_options'])

Tethys Platform leverages the native Django testing framework (which leverages the unittests Python module) to make writing tests for your app much simpler. While Tethys Platform encapsulates most of what is needed in its Testing API, it may still be necessary to refer to the Django and Python documentation for additional help while writing tests. Refer to their documentation here:

https://docs.djangoproject.com/en/1.9/topics/testing/overview/#writing-tests

https://docs.python.org/2.7/library/unittest.html#module-unittest

Testing Controllers that Use OAuth2 Authentication

Important

This feature requires the social-auth-app-django library to be installed. Starting with Tethys 5.0 or if you are using micro-tethys-platform, you will need to install social-auth-app-django using conda or pip as follows:

bash
# conda: conda-forge channel strongly recommended
conda install -c conda-forge social-auth-app-django

# pip
pip install social-auth-app-django

Using the force_login method above works great for testing controllers where login is required. However, additional steps are required to test controllers that must be authenticated with a specific OAuth2 provider (i.e. specify the ensure_oauth_provider argument to the controller decorator). For example, if you have a controller like this:

python
@controller(
  ensure_oauth2_provider="google-oauth2"
)
def home(request):
  ...

Then your test will need to define the UserSocialAuth database object to register the test user with the oauth provider. This can be done in the setUpClass method of your test class:

python
from unittest import mock
from social_django.models import UserSocialAuth
from tethys_sdk.testing import TethysTestCase


class MapControllerTestCase(TethysTestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.client = cls.get_test_client()
        cls.user = cls.create_test_user(username="joe", password="secret", email="joe@some_site.com")
        UserSocialAuth(user=cls.user, provider="google-oauth2").save()

Once the UserSocialAuth object is defined you can then use the force_login method as before:

python
def test_success_and_context(self):
    self.client.force_login(self.user)
    response = self.client.get('/apps/my-first-app/map/')

    # Check that the response returned successfully
    self.assertEqual(response.status_code, 200)

    # Check that the response returned the context variable
    self.assertIsNotNone(response.context['map_options'])

Running Tests

To run tests for an app:

  1. Open a terminal and activate the Tethys environment:

shell
conda activate tethys
  1. In portal_config.yml make sure that the default database user is set to tethys_super or is a super user of the database:

yaml
DATABASES:
    default:
        ENGINE: django.db.backends.postgresql_psycopg2
        NAME: tethys_platform
        USER: tethys_super
        PASSWORD: pass
        HOST: 127.0.0.1
        PORT: 5435
  1. From the root directory of your app, run the tethys manage test command:

shell
tethys manage test tethysapp/<app_name>/tests

This will run all tests defined in the tests directory of your app. If you would like to run just a subset of tests then you can specify which tests to run with the following:

To run all tests in a specific module within the tests directory:

shell
tethys manage test tethysapp/<app_name>/tests/<module_name>

# or

tethys manage test tethysapp.<app_name>.tests.<module_name>

To run all tests within specific class of a test module:

shell
tethys manage test tethysapp.<app_name>.tests.<module_name>.<class_name>

And to run a single test method within a test class:

shell
tethys manage test tethysapp.<app_name>.tests.<module_name>.<class_name>.<method_name>

For example, to run just the test_success_and_context test method defined above you would use the following command:

shell
tethys manage tests tethysapp.my_first_app.tests.tests.MapControllerTestCase.test_success_and_context

Important

When specifying the tests to run using a system path (e.g. tethysapp/<app_name>/tests/) you must provide either an absolute path or the path relative to your current working directory. When specifying the tests to run using a Python module (e.g. tethysapp.<app_name>.tests.<module_name>) then your current working directory is irrelevant, but note that this format will only work for modules under the tests directory. You cannot specify the whole tests directory as a Python module.

API Documentation

class tethys_apps.base.testing.testing.TethysTestCase(methodName='runTest')

This class inherits from the Django TestCase class and is itself the class that is should be inherited from when creating test case classes within your app. Note that every specific test written within your custom class inheriting from this class must begin with the word "test" or it will not be executed during testing.

static create_test_persistent_stores_for_app(app_class)

Creates temporary persistent store databases for this app to be used in testing.

Parameters:

app_class -- The app class from the app's app.py module

Returns:

None

static create_test_superuser(username, password, email=None)

Creates and returns a temporary superuser to be used in testing

Parameters:
  • username (string) -- The username for the temporary test user

  • password (string) -- The password for the temporary test user

  • email (string) -- The email address for the temporary test user

Returns:

User object

static create_test_user(username, password, email=None)

Creates and returns temporary user to be used in testing

Parameters:
  • username (string) -- The username for the temporary test user

  • password (string) -- The password for the temporary test user

  • email (string) -- The email address for the temporary test user

Returns:

User object

static destroy_test_persistent_stores_for_app(app_class)

Destroys the temporary persistent store databases for this app that were used in testing.

Parameters:

app_class -- The app class from the app's app.py module

Returns:

None

static get_test_client()

Returns a Client object to be used to mimic a browser in testing

Returns:

Client object

set_up()

This method is to be overridden by the custom test case classes that inherit from the TethysTestCase class and is used to perform any set up that is applicable to every test function that is defined within the custom test class

Returns:

None

tear_down()

This method is to be overridden by the custom test case classes that inherit from the TethysTestCase class and is used to perform any tear down that is applicable to every test function that is defined within the custom test class. It is often used in conjunction with the "set_up" function to tear down what was setup therein.

Returns:

None