Frontend testing in Django with Grunt

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:
- Ability run tests in the terminal
- Ability run tests in the browser
- Integration with TravisCI
- Simple configuration
- 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
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 [shell]mocha[/shell] as our test runner and [shell]grunt[/shell] as our task runner:
npm install -g grunt, grunt-cli npm install grunt-mocha, mocha
Or just make a [shell]package.json[/shell]. Check out ours.
Configuring the mocha Django app
We need to create an HTML file for the [shell]mocha[/shell] 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 [shell]mocha[/shell]. 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 [shell]/mocha/<app>[/shell]. 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 [shell]mocha.js[/shell], [shell]mocha.css[/shell], 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 [shell]grunt mocha[/shell] or [shell]grunt mocha:my_sweet_app[/shell] will run those javascript tests. The last setup needed is to write the template for the tests for [shell]my_sweet_app[/shell]
Writing the tests
Since we dynamically determine which app to load the template from in the [shell]MochaView[/shell], we will write the mocha test template in the [shell]my_sweet_app[/shell] app. This is convenient because all javascript tests for [shell]my_sweet_app[/shell] will live in that app itself (instead of the [shell]mocha[/shell] 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 [shell]/mocha/my_sweet_app[/shell] or run the tests in the terminal with [shell]grunt my_sweet_app[/shell].
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 [shell].travis.yml[/shell]:
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 [shell].travis.yml[/shell], specifically these two files: [shell].travis.yml[/shell], [shell].travis/matrix-installs.sh[/shell].
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 [shell]sinon.js[/shell] or [shell]blanket.js[/shell]
- 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 [shell]watch[/shell] command to your [shell]Gruntfile[/shell] 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.
Share
Tags
Similar Articles
Another day, another Zero Day: What all Digital Development organizations should take away from recent IT security news
Even if you don’t work as a software developer, you probably heard about recent, high profile security issues that had IT Admins and developers frantically patching servers over the holidays and again more recently. Dimagi's CTO shares what these recent issues mean for Digital Development organizations.
Technology
January 28, 2022
Join the fight to support critical open source infrastructure
Open Source tools are a critical piece of global infrastructure, and need champions for long term investment
Technology
March 17, 2020
Two big lessons that Iowa and Geneva can teach us about technology in digital development
Last week brought two high profile technology failures into the global spotlight. Although these two mishaps may seem quite different at first glance, they both highlight challenges that are inherent in providing software in the public sector (regardless of locale) and illustrate cautionary lessons worth discussing for practitioners in Digital Development. The Iowa Caucus Debacle
Technology
February 7, 2020