It’s incredibly important to verify that your plugin works correctly, not just for your own piece of mind and future maintainability, but also so your consumer can have some trust that your plugin does what it claims to do.

Thankfully, writing and executing tests isn’t too difficult. In fact, the tooling for testing our plugins came from the need to test Cordova’s own plugins. cordova-medic is a test tool designed to run all the core Cordova plugin tests for continuous integration. Many of these pieces of cordova-medic are reusable, so Jesse spun them into another purpose-based tool named cordova-paramedic.

cordova-paramedic

Paramedic • noun provides advanced levels of care at the point of illness or injury, including out-of-hospital treatment, and diagnostic services

Once you have tests written and placed in your plugin’s tests/ directory, cordova-paramedic` does the following for you:

All of this makes testing your plugins really simple. Before we go any further, though, let’s install it.

$ npm install cordova-paramedic

# or, from git (this was required for Windows to work)
$ npm install https://github.com/apache/cordova-paramedic.git

In order to use this, though, we need to add some scripts to our package.json file. They look like this:

  "scripts": {
    "test:android": "cordova-paramedic --cleanUpAfterRun --verbose --platform android --plugin .",
    "test:browser": "cordova-paramedic --cleanUpAfterRun --verbose --platform browser --plugin .",
    "test:ios": "cordova-paramedic --cleanUpAfterRun --verbose --platform ios --plugin .",
    "test:windows": "cordova-paramedic --config .paramedic.windows.config.js"
  }

With this in place, we can test a plugin just by executing npm run test:ios (or whichever platform you need to test with).

Note: In order to test, you’ll to have the SDKs properly installed.

Windows Note: You’ll need the following configuration file, named .paramedic.windows.config.js:

   module.exports = {
       "plugins": [ "." ],
       "platform": "windows",
       "action": "run",
       "args": "--archs=x64 -- --appx=uap",
       "verbose": true,
       "cleanUpAfterRun": true,
       "logMins": 5
   }

How to write tests

The easiest way is probably to copy a core plugin’s tests. The structure is already present, and you know the tests work. You can then adapt things as necessary.

If you want to write your own from scratch, you need to do the following:

There are two types of tests that you should be familiar with: automatic and manual tests.

Automatic tests

Automatic tests run automatically when the temporary project is launched. Your automatic tests should be exported as follows:

exports.defineAutoTests = function () {
    /* your tests */
}

Here’s a snippet from our prime plugin’s test script (full version):

exports.defineAutoTests = function () {
    describe("IsPrime (cordova.plugins.kas.isPrime)", function () {
        it("should exist", function () {
            expect(cordova.plugins.kas.isPrime).toBeDefined();
        });
        it("should be a function", function () {
            expect(typeof cordova.plugins.kas.isPrime === "function").toBe(true);
        });
        it("should throw with no arguments", function() {
            expect(function() {
                cordova.plugins.kas.isPrime();
            }).toThrowError("Success callback must be a function");
        });
        it("should throw if callbacks aren't functions", function() {
            expect(function() {
                cordova.plugins.kas.isPrime(undefined, function() {}, 23);
            }).toThrowError("Success callback must be a function");
            expect(function() {
                cordova.plugins.kas.isPrime(function() {}, undefined, 23);
            }).toThrowError("Failure callback must be a function");
        });
        it("should throw if candidate isn't a number", function() {
            expect(function() {
                cordova.plugins.kas.isPrime(function() {}, function() {}, "23");
            }).toThrowError("Candidate must be a number");
        });
        it("should return a Promise if given one argument that is a number", function() {
            expect(cordova.plugins.kas.isPrime(23) instanceof Promise).toBe(true);
        });
        /* ... more tests in this category ... */
    });
    /* ... more tests ... */
};

Manual tests

Manual tests are excellent for tests that require user interaction or tests that are hard to write verification routines for. These are controlled by a simple user interface in the temporary project. You can then tap on tests, have them run, and verify that they did what you want.

Manual tests are exported on exports.defineManualTests. Here’s a simple example from our prime plugin’s test script (some of which is copied from a core plugin’s tests — told you we copied!):

exports.defineManualTests = function (contentEl, createActionButton) {
    var actionsDiv = [
        "<h2>Actions</h2>",
        "<p>Click a button to run a test</p>",
        "<div id='simple'></div>"
    ].join("");
    function renderActions() {
        contentEl.innerHTML = actionsDiv;
    }
    // We need to wrap this code due to Windows security restrictions
    // see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
    if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
        MSApp.execUnsafeLocalFunction(renderActions);
    } else {
        renderActions();
    }
    createActionButton("Is 49 Prime?", function () {
        cordova.plugins.kas.isPrime(49)
            .then(function (result) {
                alert(result.isPrime);
            }).catch(function (err) {
                alert("Error: " + err.message);
            });
    }, "simple");
};

Testing Tips

It’s a good idea to automate as much as you possibly can; this will make it much easier to verify your plugin’s correctness as you add more functionality or fix bugs later on. If you can’t, then use a manual test, but try and think if there’s a way to automate it.

As with any Jasmine test, be sure to call done() when you’re using callbacks or promises. Here’s an example:

it("Checking " + key + (typeof expectedResult === "string" ? " has factors " + expectedResult : " is a prime"), function (done) {
    try {
        cordova.plugins.kas.isPrime(function win(result) {
            if (result.complete) {
                if (typeof expectedResult === "string") {
                    expect(result.isPrime).toBe(false);
                    expect(result.factors.join(", ")).toBe(expectedResult);
                } else {
                    expect(result.isPrime).toBe(expectedResult);
                }
                done();
            }
        }, function fail(err) {
            expect("this should never happen").toBe("but it did:" + JSON.stringify(err));
            done();
        }, Number(key));
    } catch (err) {
        expect("this is embarrasing").toBe(err.message);
        done();
    }
}, 120000);

The above example also demonstrates two more useful tips:

Continuous Integration using Travis CI

Testing with Travis CI makes it easy to verify that your last code changes didn’t totally break your plugin. To enable Travis CI support, add a .travis.yml to your project root that looks like the following:

sudo: false
matrix:
  include:
    - os: osx
      language: objective-c
    - os: linux
      jdk: oraclejdk1.8
      language: android
android:
  components:
    - tools
    - platform-tools
    - tools
    - build-tools-25.0.2
    - android-25
    - sys-img-armeabi-v7a-android-N
  licenses:
    - 'android-sdk-preview-license-.+'
    - 'android-sdk-license-.+'
    - 'google-gdk-license-.+'
script:
  - nvm install 6.10.2
  - nvm use 6.10.2
  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then rvm use system; fi
  - /bin/bash tests/travis.sh

Then add the following script to your test directory, named travis.sh:

#!/bin/bash
set -o nounset
set -o errexit

npm install -g cordova
npm install

# lint
npm run lint

# run tests appropriate for platform
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
    sudo gem install cocoapods
    npm install -g ios-sim ios-deploy
    npm run test:ios
fi
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
    echo no | android create avd --force -n test -t android-21 --abi armeabi-v7a
    emulator -avd test -no-audio -no-window &
    android-wait-for-emulator
    npm run test:android
fi

Navigation: