Intern

Until a few years ago, our testing efforts with Dojo were focused on the Dojo Object Harness (DOH), a very early unit and functional testing suite. Developed by the Dojo Toolkit community nearly ten years ago, DOH’s main purpose was to provide functionality for unit testing JavaScript functions and custom widgets in a cross-browser compatible way.

As part of our efforts toward Dojo 2, we knew we needed something much better than DOH, which led to our work on Intern.

For the Dojo 1.11 release, we spent time updating to a more modern testing framework and converted all DOH-based tests in Dojo core to use Intern. This will allow the Dojo Toolkit to ensure code coverage across the toolkit and also allow streamlined regression testing to more quickly accept fixes and patches from the community.

Comparing DOH to Intern

DOH Logo

When stacked side by side DOH and Intern may provide similar functionality, but there are many things that set them apart.

One major difference between DOH and Intern: when DOH was originally created in 2005, Selenium did not exist. This allowed developers to run DOH directly from a browser. Later DOH added a Java-based applet (DOH Robot) to enable functional testing with Selenium 1. Intern, on the other hand, was designed with Selenium 2 testing in mind, leveraging the WebDriver protocol.

DOH was also generally run from a web server with PHP installed. This means that any test which needed to make a call to a remote service had to be done in PHP. Intern runs within Node.js, so services can be written in JavaScript.

Further, Intern has taken advantage of modern projects to make testing easier. Grunt, a JavaScript task runner, is a great tool for running repetitive tasks. Intern includes a Grunt task for invoking the intern runner quickly. Intern can also easily be used within a Gulp workflow.

In addition, Intern provides support for some of your favorite continuous integration services like Travis, Jenkins, Bamboo, and TeamCity.

Intern also provides a number of great features which DOH does not, like promise-based async from any function, code coverage analysis, and source map support.

Intern, cloud testing and DigDug

DigDug Logo

Another advantage that Intern has over DOH is the functionality to provide secure connections with third party Selenium services like Sauce Labs and BrowserStack. The Intern project provides a standalone library, DigDug, for this functionality. With Intern, we can set options and run our functional and unit tests on a cloud testing provider over a tunnel.

DOH and Intern unit testing

With DOH, a limited assertion library was created for authoring tests.

Intern opted to use the Chai library for its assertions which comes with a plethora of assertions. Chai works great as an assertion library and is familiar to many developers. Of course, you don’t have to use Chai. Thanks to Intern’s modular design, you can easily use any assertion library you wish.

DOH Robot and Intern Functional Testing with Leadfoot

Leadfoot Logo

DOH Robot provided an API that allowed for functional testing on the client-side. This allowed developers to write and run functional tests across many platforms to test user interactions with real events via a Java applet.

Intern also allows for functional testing, in a much easier way. Intern includes Leadfoot, a robust API for driving web browsers insanely fast.

Leadfoot not only makes it much simpler to add functional tests than DOH Robot, but also provides more stability. Leadfoot detects inconsistencies with the WebDriver implementations and provides a simple API so developers can write tests rather than debugging issues with Selenium driver instances.

We also offer a Chrome DevTools extension, Intern Recorder, to make it easier to get started with authoring functional tests.

The Tests

Let’s take a look at some tests that were converted from DOH to Intern unit tests for the Dojo 1.11 release. Here is the unit test for dojo/_base/Color written with DOH:

	define(["doh/main", "../_base/array", "../_base/Color", "../colors"], function(doh, array, Color, colors){

		var verifyColor = function(t, source, expected){
			source	 = new Color(source);
			expected = new Color(expected);
			t.is(expected.toRgba(), source.toRgba());
			array.forEach(source.toRgba(), function(n){ t.is("number", typeof(n)); });
		};

		doh.register("tests.colors", [
			// all tests below are taken from #4.2 of the CSS3 Color Module
			function testColorEx01(t){ verifyColor(t, "black", [0, 0, 0]); },
			function testColorEx02(t){ verifyColor(t, "white", [255, 255, 255]); },
			function testColorEx03(t){ verifyColor(t, "maroon", [128, 0, 0]); },
			function testColorEx04(t){ verifyColor(t, "olive", [128, 128, 0]); },
			function testColorEx05(t){ verifyColor(t, "#f00", "red"); },
			function testColorEx06(t){ verifyColor(t, "#ff0000", "red"); },
			function testColorEx07(t){ verifyColor(t, "rgb(255, 0, 0)", "red"); },
			function testColorEx08(t){ verifyColor(t, "rgb(100%, 0%, 0%)", "red"); },
			function testColorEx09(t){ verifyColor(t, "rgb(300, 0, 0)", "red"); },
			function testColorEx10(t){ verifyColor(t, "rgb(255, -10, 0)", "red"); },
			function testColorEx11(t){ verifyColor(t, "rgb(110%, 0%, 0%)", "red"); },
			function testColorEx12(t){ verifyColor(t, "rgba(255, 0, 0, 1)", "red"); },
			function testColorEx13(t){ verifyColor(t, "rgba(100%, 0%, 0%, 1)", "red"); },
			function testColorEx14(t){ verifyColor(t, "rgba(0, 0, 255, 0.5)", [0, 0, 255, 0.5]); },
			function testColorEx15(t){ verifyColor(t, "rgba(100%, 50%, 0%, 0.1)", [255, 128, 0, 0.1]); },
			function testColorEx16(t){ verifyColor(t, "hsl(0, 100%, 50%)", "red"); },
			function testColorEx17(t){ verifyColor(t, "hsl(120, 100%, 50%)", "lime"); },
			function testColorEx18(t){ verifyColor(t, "hsl(120, 100%, 25%)", "green"); },
			function testColorEx19(t){ verifyColor(t, "hsl(120, 100%, 75%)", "#80ff80"); },
			function testColorEx20(t){ verifyColor(t, "hsl(120, 50%, 50%)", "#40c040"); },
			function testColorEx21(t){ verifyColor(t, "hsla(120, 100%, 50%, 1)", "lime"); },
			function testColorEx22(t){ verifyColor(t, "hsla(240, 100%, 50%, 0.5)", [0, 0, 255, 0.5]); },
			function testColorEx23(t){ verifyColor(t, "hsla(30, 100%, 50%, 0.1)", [255, 128, 0, 0.1]); },
			function testColorEx24(t){ verifyColor(t, "transparent", [0, 0, 0, 0]); },
			// all tests below test greyscale colors
			function testColorEx25(t){ verifyColor(t, colors.makeGrey(5), [5, 5, 5, 1]); },
			function testColorEx26(t){ verifyColor(t, colors.makeGrey(2, 0.3), [2, 2, 2, 0.3]); }
		]);
	});

Here is that same unit test converted to Intern:

	define([
		'intern!object',
		'intern/chai!assert',
		'../../_base/Color',
		'../../colors'
	], function (registerSuite, assert, Color, colors) {

		/**
		 * array or arrays to add for tests - actual and expected indexes
		 * @type {Array}
		 */
		var testColorsObject = [
			[ 'black', [0, 0, 0] ],
			[ 'white', [255, 255, 255] ],
			[ 'maroon', [128, 0, 0] ],
			[ 'olive', [128, 128, 0] ],
			[ '#f00', 'red' ],
			[ '#ff0000', 'red' ],
			[ 'rgb(255, 0, 0)', 'red' ],
			[ 'rgb(100%, 0%, 0%)', 'red' ],
			[ 'rgb(300, 0, 0)', 'red' ],
			[ 'rgb(255, -10, 0)', 'red' ],
			[ 'rgb(110%, 0%, 0%)', 'red' ],
			[ 'rgba(255, 0, 0, 1)', 'red' ],
			[ 'rgba(100%, 0%, 0%, 1)', 'red' ],
			[ 'rgba(0, 0, 255, 0.5)', [0, 0, 255, 0.5] ],
			[ 'rgba(100%, 50%, 0%, 0.1)', [255, 128, 0, 0.1] ],
			[ 'hsl(0, 100%, 50%)', 'red' ],
			[ 'hsl(120, 100%, 50%)', 'lime' ],
			[ 'hsl(120, 100%, 25%)', 'green' ],
			[ 'hsl(120, 100%, 75%)', '#80ff80' ],
			[ 'hsl(120, 50%, 50%)', '#40c040' ],
			[ 'hsla(120, 100%, 50%, 1)', 'lime' ],
			[ 'hsla(240, 100%, 50%, 0.5)', [0, 0, 255, 0.5] ],
			[ 'hsla(30, 100%, 50%, 0.1)', [255, 128, 0, 0.1] ],
			[ 'transparent', [0, 0, 0, 0] ],
			[ colors.makeGrey(5), [5, 5, 5, 1] ],
			[ colors.makeGrey(2, 0.3), [2, 2, 2, 0.3] ]
		];

		registerSuite({
			name: 'dojo/colors',

			'test colors': function () {
				var i,
					k,
					actual,
					expected;

				for (i = 0; i < testColorsObject.length; i += 1) {
					actual = new Color(testColorsObject[i][0]);
					expected = new Color(testColorsObject[i][1]);

					assert.deepEqual(actual.toRgba(), expected.toRgba());

					for (k = 0; k < actual.length; k += 1) {
						assert.isNumber(actual[k]);
					}
				}
			}
		});
	});

While this is a simple example, the differences between these two tests are pretty noticeable. registerSuite (what we use to register an Intern suite) takes a single object argument. This object is where you place tests and define various helper methods that Intern provides like before, beforeEach, after and afterEach.

The name property is used by Intern so you can see which tests belong to which suite at a glance. This is helpful when you have multiple suites to run, making it easy to group tests logically. In addition, any object property added to the object passed to registerSuite can be used for a group of tests. In this case, you see we just have one method called testColors. This could just as easily be put in its own object property along with other pertinent tests. This allows us to be much more organized when writing our unit tests.

In the first test you will see doh.register accepts a name and an array of functions as its parameters. With the converted test, we made one private variable whose value is an array of arrays. We’ve set up one method, test colors, that runs a for loop to test all of the values. This is much simpler than defining many functions to run.

While they look different, both of these tests actually test the same functionality. However, Intern provides a much greater range of features, ease of use and readability.

DOH2Intern

With the many advantages that the Intern testing suite has over the DOH library, it’s easy to see why we have made these changes. The SitePen team worked very hard to upgrade old DOH unit tests to Intern, and took advantage of all the features that Intern makes available. The team also cleaned up all DOH tests that were found to be redundant or out of date. This improved the quality of the unit tests and code coverage in the Dojo toolkit immensely.

Learning more

DOH Logo

We cover Intern in our Intern and Dojo 202 workshops, and we support developers worldwide in their efforts with JavaScript and testing. If you would like to discuss how we can help your organization improve their approach to automated testing, please contact us to start the conversation.