At Dimagi, we’ve been aiming to implement a framework to run browser javascript tests for our Django application, commcare-hq. Our Django application is large and thus requires a flexible framework to run the tests. Here are the requirements we had for our framework:

  1. Ability run tests in the terminal
  2. Ability run tests in the browser
  3. Integration with TravisCI
  4. Simple configuration
  5. Ability to handle multiple dependencies (i.e. run one test suite with an older version of jQuery and another version with a newer one. Or run a test suite with only a subset of the javascript files)

Architecture Overview

Conceptual Structure

Frontend Django App architecture

File Structure

apps/
  mocha/
    urls.py
    views.py
    templates/
      mocha/
        base.html  # Base Mocha template that will be extended from other apps
  my_sweet_app
    urls.py
    views.py
    models.py
    ...
    templates/
      my_sweet_app/
        spec/
          mocha.html  # extends the base template in the mocha app
    static/
      my_sweet_app/
        js/
          file_to_test.js
        spec/
          tests.js

Requirements

To get started we settled on using mocha as our test runner and grunt as our task runner:

npm install -g grunt, grunt-cli
npm install grunt-mocha, mocha

Or just make a package.json. Check out ours.

Configuring the mocha Django app

We need to create an HTML file for the mocha test runner to run. There are a couple of strategies to do this, but we decided to take advantage of all the great templating and routing Django had to offer by writing a view that rendered an HTML file. To accomplish this, create a new Django application called mocha. In the app, we’ll need a view, a template, and a route to render the base test runner file. This assumes basic knowledge of writing views in Django.

The view can be as simple as this:

# apps/mocha/views.py
class MochaView(TemplateView):
    def dispatch(self, request, *args, **kwargs):
        # Dynamically specifying the app in the url allows us to reuse code more easily
        self.template_name = '{}/spec/mocha.html'.format(kwargs['app'])
        return super(MochaView, self).dispatch(request, *args, **kwargs)

Now hook that view up to a route. In CommCareHQ we use /mocha/<app>. Since we will be using the Django templating processor, write the template like any other template you’d write. Ours looks like this. Ensure that you include mocha.js, mocha.css, and a block for including app dependencies:

# apps/mocha/templates/mocha/base.html
<head>
    <!-- Mocha and test-only dependencies -->
    <link href="{% static 'mocha/mocha.css' %}" type="text/css" rel="stylesheet"></link>
    <script src="{% static 'mocha/mocha.js' %}"></script>
    <script src="{% static 'chai/chai.js' %}"></script>

    <!-- App specific dependencies -->
    {% block dependencies %}{% endblock %}
</head>

In the body you’ll minimally need to have a block for the mocha tests and start the mocha runner:

# apps/mocha/templates/mocha/base.html

<body>
    {% block mocha_tests %}{% endblock %}
    <script type="text/javascript" charset="utf-8">
    if (navigator.userAgent.indexOf('PhantomJS') < 0) {
      mocha.run();
    }
    </script>
</body>

Configuring Grunt

In order to run the tests from the terminal, and also TravisCI, we’ll need Grunt. Point the configuration at the route you just setup. It should look something like this:

// Gruntfile.js
grunt.initConfig({
    mocha: {
        'my_sweet_app': {
            options: {
                urls: ['http://localhost:8000/mocha/my_sweet_app/'],
                run: true,
                logErrors: true
            }
        }
        // ...add more apps you would like to test
    }
});

grunt.loadNpmTasks('grunt-mocha');

Running grunt mocha or grunt mocha:my_sweet_app will run those javascript tests. The last setup needed is to write the template for the tests for my_sweet_app

Writing the tests

Since we dynamically determine which app to load the template from in the MochaView, we will write the mocha test template in the my_sweet_app app. This is convenient because all javascript tests for my_sweet_app will live in that app itself (instead of the mocha app), following backend test conventions in Django.

# apps/my_sweet_app/templates/my_sweet_app/spec/mocha.html

<!-- mocha.html in my_sweet_app -->
{% extends "mocha/base.html" %}

{% block dependencies %}
<!-- all files that need to be loaded for the tests to run -->
{% endblock %}

{% block mocha_tests %}
<script src="{% static 'my_sweet_app/path/to/spec.js' %}"></script>
{% endblock %}

With that, you should be able to either run your tests in the browser by navigating to /mocha/my_sweet_app or run the tests in the terminal with grunt my_sweet_app.

TravisCI integration

Most of the heavy lifting has been done at this point. At the very least, you’ll need to add this to your .travis.yml:

install:
    - npm install -g grunt, grunt-cli
    - npm install
    - python manage.py migrate --noinput
    - python manage.py runserver 8000 &
    - sleep 5  # wait for server to start
script: 'grunt mocha'

This is probably the quickest and dirtiest way to get this to work. If you want a more robust approach, take a look at our .travis.yml, specifically these two files: .travis.yml, .travis/matrix-installs.sh.

Bonuses

Above is the simplest way to get started with frontend testing with your Django app. There are quite a few things you can do to spruce up your frontend testing stack. Here are a few things we’ve done:

  • Add in more test dependencies that make javascript testing fun, like sinon.js or blanket.js
  • Allow for multiple javascript configurations for a single app. This is useful if your Django app has code that runs on multiple pages with differently versioned libraries
  • Use TravisCI’s matrix capabilities to run Javascript tests independently of your python tests
  • Add a watch command to your Gruntfile so your tests automatically run as you save your files

If you’re interested in connecting with Dimagi to learn more about CommCare, please reach out here