Great apps—like great artists, actors, and athletes—are great performers. Human beings validate and continually improve their performances through extensive training, practice, rehearsals, and after-the-fact reviews. We achieve these same ends in software development through many levels of rigorous testing—or continuous validation—across the entire app lifecycle.
Chances are you’ve heard about unit testing, but you might not be clear on what it is, exactly, and how it fits into a cross-platform app project build with Apache Cordova. In the documentation for the Visual Studio Tools for Apache Cordova, we’ve recently added a whole section Author & run tests to explore the details of unit testing with Cordova apps through a series of comprehensive examples.
In this two-part post I’ll give a shorter version of those walkthroughs to familiarize you with the process. Part 1 here provides background on unit testing and a basic example with Cordova and Visual Studio. Part 2 will delve a little deeper into the subject of test-driven development and debugging unit tests.
Unit testing vs. other forms of testing
Unit testing is unique from other forms of manual and automated testing in a number of ways:
Unit Testing | Other Testing |
Tests “units” of app code (classes, methods, properties, and functions) by calling/using them directly. | Tests the built app that’s been deployed to emulators or tests device through its user interface. |
Is aware of how the app code is written (“white box testing”) | Is not aware of how the app was written (“black box testing”) |
Tests are run on development and build machines via a test runner (for example, an independent JavaScript runtime like Node.js). | Tests are run on emulators or physical devices. |
Tests are themselves pieces of code (typically written in the same language as the app code, e.g. JavaScript) | Tests are scripts (written manually or generated by a recorder) or are simply a set of steps that a tester performs manually. |
Occurs early in the dev cycle, alongside coding or even before coding begins (test-driven development). | Occurs after a successful build of the app. |
Ideally run very quickly so they can be performed with each build, which helps to quickly catch regressions introduced with code changes. | Are not time-sensitive (depending on one’s release management process) |
Failed tests invalidate builds or commits to version control repositories. | Failed tests invalidate steps in a release management process. |
The following figure illustrates how most forms of app testing (left) differ from unit testing (right), and illustrates that unit tests are typically run outside the app platform itself.
Note: when you set out to do unit testing, focus wholly on the code that you write or modify. You need not test libraries or other code you’re importing into a project but not modifying, as the authors of those libraries should have already tested them. If tests are lacking for a library you want to use, the right place to do that work is in the library’s source repo, not in your project.
Unit testing environments for JavaScript and Apache Cordova
A unit testing environment consists of the three components: the runtime, a test framework, and a test runner. The following table describes these and gives some examples.
Component | Description | Examples for JavaScript |
Runtime | Loads and executes code being tested outside the app. This can happen in a browser or a separate runtime sometimes referred to as a “headless browser.” | Browsers, or Node.js, PhantomJS, Chrome V8 |
Test framework | Defines how to write pieces of code that are specifically identified as “tests,” typically in the same language as the code being tested so they can share the same runtime, but this is not required. | Jasmine, QUnit, Mocha along with libraries like Chai and assert.js |
Test runner | Executes tests defined by a supported framework within a supported runtime, and produces test reports. | Chutzpah (uses PhantomJS), Karma (uses a browser) |
All of these relationships are illustrated below:
Unit testing is then a matter of invoking the test runner at appropriate times and viewing the reports. This can be done manually or as part of an automated build process, and the test runner might be invoked through the command line or through integration with the Visual Studio IDE.
Some test runners, like Chutzpah, have a Visual Studio test adapter that integrates the tool and its reporting into Visual Studio’s Test Explorer. Other test runners, like Karma, don’t have a test adapter but can still be integrated into Visual Studio’s Task Runner Explorer in conjunction with tools like Grunt and gulp. Both options are covered in the documentation; for Karma and gulp, see Basic unit testing and Test Apache Cordova apps with Karma and Jasmine.
Unit testing in action
To better understand the mechanics of unit testing, let’s now follow a piece of code and an associated unit test through the basic process with Chutzpah, QUnit (jQuery’s unit test framework), and Visual Studio.
The “unit”
First, assuming you have the Visual Studio Tools for Apache Cordova installed, create a new app project through File > New Project, selecting JavaScript > Apache Cordova Apps > Blank App. Then in the www/scripts folder create a file called normalize.js with the code below.
/** @description Converts JSON data that may contain Name and PersonalIdentifier * properties to an object with the properties name (string) and id (positive * integer up to 9999999999. * @param {string} jsonIn The JSON data to normalize. * @return {object} An object with name (string) and id (integer) properties, * defaulting to "default" and 0, or null if the JSON is null or invalid. */ function normalizeData(jsonIn) { }
This simple normalizeData function is the “unit” we’ll be testing, and we’ll leave it empty for the moment. Understand that this code is strictly part of the app’s functionality: it has nothing whatsoever to do with our choice of test framework or test runner.
The unit tests
Next, each unit test is a piece of code that validates a unit by:
- Calling it with specific inputs, and,
- Checking the output against the expected value.
A unit test must follow the conventions of the test framework you’re using. Because we’re testing JavaScript code, it’s easiest to use one of the many JavaScript test frameworks available to us. For this example, we’ll use the one called Jasmine. It enjoys built-in support with the Chutzpah test runner that we’ll be using shortly, so we don’t need to install anything for Jasmine specifically.
For the unit tests, create a folder named test in the project, then create a file in that folder called normalize_tests.js with the following contents:
// First argument to "describe" is the name of this group of tests describe("normalizeData tests", function () { // First argument to "it" is the name of the specific test it("accepts golden path data", function () { // Use the unit being tested as necessary var json = '{"Name": "Maria", "PersonalIdentifier": 2111858}'; var norm = normalizeData(json); // Check the results; "expect" is a Jasmine method. expect(norm.name).toEqual("Maria"); expect(norm.id).toEqual(2111858); }); });
In Jasmine, you create a group of tests with its describe method, and each test is a call to a function named it, which ends up reading quite well with the description of the test itself. Within each test you use the unit code however you want, and then check results with Jasmine’s expect method that generates the appropriate pass/fail report output.
We have just one simple test to start with here, but notice how it’s specific: it calls the unit under test with one set of inputs and gives an exact name/description for the test with those inputs. This follows the best practice of isolating each unit test to an individual test case, creating a 1:1 mapping between the name of the test, as it appears in reports, and the exact test case (that is, the arguments used in the test). When the test runner reports a failure, then, you know exactly where to look in your code and can easily step through that one test in the debugger to isolate the failure. This saves you time in the end, because if you combine tests, you’ll have to debug the whole set to find the one that failed, and you’ll likely end up separating all the tests anyway.
The test runner
With unit code and at least one unit test in place, we can now run the tests with the Chutzpah test runner. To install it, go to Tools > Extensions and Updates… in Visual Studio, select Online, then search for and install the “Chutzpah Test Adapter” (you’ll be prompted to restart Visual Studio):
To tell Chutzpah about the files it should work with, create a Chutzpah.json file in the project’s root folder with the following contents:
{ "References": [ { "Path": "www/scripts/js/normalize.js" } ], "Tests": [ { "Path": "test/normalize_tests.js" } ] }
The “References” section clearly identifies the code to test and the “Tests” section identifies the unit test files. You’d obviously list more code and test files as you add them to a project.
Now select Test > Windows > Test Explorer and you should see the available tests listed there:
Note: if nothing appears, there are likely syntax errors in your test files which prevent Chutzpah from finding those tests.
Click Run All to build the project, run the tests, and see the results. Because we don’t have any implementation in normalizeData, the test should fail:
This verifies that you have the mechanics of using Chutzpah working within Visual Studio.
If you’d like to make the test pass, implement normalizeData soit at least handles expected inputs and run the test again:
function normalizeData(jsonIn) { data = JSON.parse(jsonIn); return { name: data.Name, id: Number(data.PersonalIdentifier) }; }
Of course, this code can’t handle anything other than perfect JSON, so there’s definitely room for improvement! We’ll pick up that story in Part 2 where we’ll talk about test-driven development and debugging unit tests.
In the meantime, I’d love to hear how you work with unit testing in Cordova apps, how you’re working with UI testing (both manual and automated), and how we can further improve our support through the Visual Studio Tools for Apache Cordova. Add a comment below, drop me a line at kraigb (at) microsoft.com, or submit suggestions via http://visualstudio.uservoice.com/.
Happy testing!
Kraig Brockschmidt, Senior Content Developer, Visual Studio Kraig has been around Microsoft since 1988, working in roles that always have to do with helping developers write great software. Currently he’s focused on developing content for cross-platform mobile app development with both Xamarin and Cordova. He writes for MSDN Magazine, is the author of Programming Windows Store Apps with HTML, CSS and JavaScript (two editions) and Inside OLE (two editions, if you remember those) from Microsoft Press, occasionally blogs on kraigbrockschmidt.com, and can be found lurking around a variety of developer conferences. |