A unit testing environment consists of the three components: the runtime, a test framework, and a test runner:
|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; 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)|
With these components in place, it’s now 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.
All of these relationships are illustrated below:
As noted in the Primer, unit testing runs code inside a runtime that’s potentially different from the one that the running app will use on a mobile platform, which means there are potential behavioral differences. This can affect test debugging, as described in Runtime variances in the debugging topic. It also makes it necessary to do special handling for unit code that calls platform APIs, as described in Using mocks for platform APIs, plugins, and other external dependencies.
Such differences can also affect the performance of the final app on the mobile platform. This is where a complete set of automated functional tests provides the next layer of validation. If those automated tests exercise all the app’s features that rely on the behaviors of your unit-tested code, then those test should catch any variations that might exist between the runtimes.