Using Karma for quick low-level integration testing
This guide follows Volta Jina’s approach to Angular testing, where Karma specs are written for logic embedded in the templates and validation is done by checking element behavior. Some nice examples of this strategy can be found at the Angular Material project.
We will be using the ionic ‘tabs’ starter as a base. Testing Ionic apps has its idiosyncrasies: ui-router and ionicTemplateCache trigger lots of ‘unexpected request’ errors from the test runner - in fact it’s common practice to disable them. This means accessing templates and their controllers requires some extra steps. We’ll walk through these, write tests for a tab view and then look at a directive that uses cordova plugins.
(None of these packages will ship with your app - they are part of the development environment)
At the command line in your project directory run:
Create a karma config file by running:
Karma will ask you a series of questions about which frameworks to use (jasmine), what browsers to launch (chrome) and what files to watch. Skip through this, pressing return after everything. There’s a section below on modifying the config file manually once it’s generated.
Add the following task to your project’s gulpfile.js:
Ultimately, when you’re ready to test you’ll run:
Make some folders for your tests
(Or think about where to put them).
You could, for example, run the following in the project’s root directory:
There is another, perhaps better strategy advocated by John Papa which says you should store tests alongside their targets - i.e maintain a directory structure like this:
The idea here is that the tests are an integral part of the code and should be kept close at hand. This project is small and doesn’t have much directory structure so we’ll just put our tests in a dedicated folder.
Edit karma.config.js
(The full config for this project can be found here.)
List jQuery first in the files array, then the ionic bundle and other lib files, then your html files, then any other js files you’ve declared in index.html and your test files.
Define your custom launcher, enumerate your plugins, specify your pre-processors, and select your reporter, as below.
Now Karma will launch in chrome, pre-cache your templates (to avoid router calls) and print intelligible color-coded reports. Lets write some tests.
Testing ChatsCtrl
That’s ChatsCtrl: an archetypically ‘thin’ controller whose sole purpose is to expose service methods to the DOM on $scope. A traditional unit test for it looks like this:
We also want tests that describe the way the controller is wired into the html since that’s where most of the logic actually gets expressed. Here’s the template: it ng-repeats a list. Each item is ng-clickable and has a dynamically generated link.
And the test set up:
Now we can access the tab-chats DOM through ‘template’. Let’s make sure chats are actually getting listed, the delete button works, and each chat item links to the right view:
Testing a directive that uses an ng-cordova plugin:
Let’s sketch a small directive that adds a chat sender’s name to the device’s contacts. NgCordova comes with its own set of mocks to help you develop in the browser. A nice tutorial for setting up your project to toggle between mock/browser and cordova/device builds can be found here. Fortunately, you can use the mocks in your tests without having to write an intricate build script. Just add them after ng-cordova in the karma.config.js files declaration.
Then load the ngCordovaMocks module after your app module at the top of a test. The mock methods will override the real ones.
Here’s our directive template: it’s a footer bar with a button inviting you to add a contact. It’s meant to sit at the bottom of the chats-detail view.
And here’s the directive: It binds an object to the ‘contact’ attribute and has a method called ‘createContact’ that uses $cordovaContacts.
This directive is pretty fake but it has all the problems a real one would have: isolate scope, a service dependency that needs to be mocked and code inside a promise callback. We set the test up like this:
To mock the ‘contact’ attribute value we’ve created a variable on $rootScope and referenced it in the DOM string that’s getting compiled. Then we’ve accessed the directive’s own scope by calling angular.element’s isolateScope() on the compiled directive. Let’s test the button:
Using Jasmine’s callThrough method we can go from the button element down to the core of createContact() and verify that $cordovaContacts gets called with the correct data. ($cordovaContacts has to be called through as well or the underlying code will throw an error when it hits the ‘then’ statement).
Let’s test the code inside the promise callback and use an ng-cordova-mocks feature that lets you emulate callback error by setting a service’s ‘throwsError’ field to ‘true’:
Run the tests
Run $ gulp test to see the report:
Contact
Feel free to ask questions or make suggestions via the issues page for this project. There are no special guidelines - just open an issue and write.