Configuring Karma for Bower Dependencies

October 15th 2016 AngularJS TypeScript Unit Testing

Although, I have now configured my Cordova project to automatically copy distribution files from installed Bower packages and reference them from HTML pages, there's still one final part of Bower-related configuration left: unit tests also require Bower dependencies. I don't want to manually update the configuration of my test runner every time I install an additional Bower package.

Since I already described basic Karma configuration for a TypeScript project in a previous blog post, I'm going to take that as my starting point and focus on automatically referencing the Bower packages. As expected, there's a plugin available for that: wiredep. At least for me, its name made it more difficult to find and identify as the right solution for my problem. However, it turned out really easy to use:

  1. Install the plugin:

     npm install --save-dev karma-wiredep
    
  2. Add it as a framework to karma.conf.js:

     frameworks: ["wiredep", "jasmine"]
    
  3. Add its own configuration block karma.comf.js:

     wiredep: {
         dependencies: true,
         devDependencies: true
     }
    

There are some additional configuration options available, but at least for now, this works for me.

As you can see, I chose to include both production and development dependencies. The reason for that is Angular, which I am using in my project. To unit test AngularJS code effectively, angular-mocks is invaluable. In my very first test, I already used its module loader and mocked $httpBackend.

Of course, you don't want to have the mocks included in your application, therefore the package needs to be installed as a development dependency:

bower install angular-mocks --save-dev

Because I'm still in the project setup phase, there's not a lot of application code to test. To try out the configuration, I decided to write a couple of tests for the AngularJS routing in its current state:

class Routes {
    static $inject = ["$locationProvider", "$routeProvider"];

    constructor($locationProvider: ng.ILocationProvider, $routeProvider: ng.route.IRouteProvider) {
        $locationProvider.hashPrefix("!");

        $routeProvider
            .when("/title",
            {
                templateUrl: "views/title.html"
            })
            .when("/game",
            {
                templateUrl: "views/game.html"
            })
            .otherwise({
                redirectTo: "/title"
            });
    }
}

angular.module("app")
    .config(Routes);

To be honest, the tests are rather trivial. I'm just checking that the right template is being used for each path:

describe("app routes", () => {

    var $route: ng.route.IRouteService;
    var $httpBackend: ng.IHttpBackendService;
    var $location: ng.ILocationService;
    var $rootScope: ng.IRootScopeService;

    beforeEach(angular.mock.module("app"));

    beforeEach(() => {
        inject((_$route_: ng.route.IRouteService, _$httpBackend_: ng.IHttpBackendService, _$location_: ng.ILocationService, _$rootScope_: ng.IRootScopeService) => {
            $route = _$route_;
            $httpBackend = _$httpBackend_;
            $location = _$location_;
            $rootScope = _$rootScope_;
        });
    });

    beforeEach(() => {
        expect($route.current).toBeUndefined();
        $httpBackend.expectGET(/.*/).respond(200);
    });

    it("route title to title.html", () => {
        $location.path("/title");
        $rootScope.$digest();
        expect($route.current.templateUrl).toBe("views/title.html");
    });

    it("route game to game.html", () => {
        $location.path("/game");
        $rootScope.$digest();
        expect($route.current.templateUrl).toBe("views/game.html");
    });

    it("route unknown paths to title.html", () => {
        $location.path("/unknown");
        $rootScope.$digest();
        expect($route.current.templateUrl).toBe("views/title.html");
    });
});

As simple as these tests are, they are enough to prove that Karma is correctly configured. All dependencies were loaded successfully, otherwise the tests would fail. Any dependencies that I might add later, will automatically be loaded as well, minimizing the overhead of writing unit tests, which is always a good thing.

Copyright
Creative Commons License