3

I am writing a unit test for karma and can't get it to run. It seems to be breaking on the inject function. I think it has to do with how I get the controller in the test, but cannot find a solution.

I just started using angular in the last few days so any advice would be appreciated, thanks!

Error:

Error: Argument 'fn' is not a function, got string
  at Error (<anonymous>)
  at $a (path/app/lib/angular.js:16:453)
  at qa (path/app/lib/angular.js:17:56)
  at Cb (path/app/lib/angular.js:24:458)
  at Object.d [as invoke] (path/app/lib/angular.js:27:66)
  at path/app/lib/angular.js:26:194
  at Array.forEach (native)
  at m (path/app/lib/angular.js:6:192)
  at e (path/app/lib/angular.js:25:298)
  at Object.sb [as injector] (path/app/lib/angular.js:29:360)
TypeError: Cannot read property 'zip' of undefined
  at null.<anonymous> (path/test/unit/controllersSpec.js:26:19)

Test:

'use strict';

/* jasmine specs for controllers go here */

describe('influences controllers', function() {
  beforeEach(module('influences.controllers', ['ui.bootstrap', 'influences.services']));

  describe('IndividualCtrl', function(){

    var scope, ctrl, service, $httpBackend;

    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller, Api_sunlight_get) {
      console.log('*** IN INJECT!!***: ', Api_sunlight_get);
      $httpBackend = _$httpBackend_;
      // ignore for now... this is an example of how I might implement this later
      // $httpBackend.expectGET('data/products.json').
      //     respond([{name: 'Celeri'}, {name: 'Panais'}]);

      scope = $rootScope.$new();
      service = Api_sunlight_get;
      ctrl = $controller('IndividualCtrl', {$scope: scope, Api_sunlight_get: service
      });
    }));

    it('should create "products" model with 2 products fetched from xhr', function() {
      console.log('*** IN TEST!!***: ', scope);
      expect(scope.zip).toEqual(12345);
    });
  });
});

Controller:

angular
  .module('influences.controllers', ['ui.bootstrap', 'influences.services'])
  .controller('IndividualCtrl', ['$scope', 'Api_sunlight_get', ($scope, Api_sunlight_get)->
    # set default variables
    $scope.zip = $scope.zip or 94102  # set default zip if one is not chosen

    # Define Methods
    $scope.get_rep_data_by_zip = ()->
      $scope.reps =  Api_sunlight_get "legislators/locate?zip=#{$scope.zip}" $scope.update_rep_data_by_zip

    $scope.update_rep_data_by_zip = ()->
      $scope.selected_rep = $scope.reps  # sets default selection for reps buttons
      for rep in $scope.reps
        rep.fullname = "" + rep.title + " " + rep.first_name + " " + rep.last_name

    # watchers
    $scope.$watch('zip', $scope.get_rep_data_by_zip)

    # initial run
    $scope.get_rep_data_by_zip()

Service:

angular
  .module('influences.services', [])
  .factory 'Api_sunlight_get', ['$http', ($http)->
    return (path, callback)->
      $http
        url: "http://congress.api.sunlightfoundation.com/#{path}&apikey=xxxx"
        method: "GET"
      .success (data, status, headers, config)->
        callback data.results
      .error (data, status, headers, config)->
        console.log("Error pulling #{path} from Sunlight API!")
  ]
EpiphanyMachine
  • 698
  • 1
  • 10
  • 8

1 Answers1

9

I think this is a problem of the dependencies of the module influences.controllers not being loaded. I tried a rather scaled down version of your program and got the exact same error as you. It was only after consulting this question here that I managed to get it. Do read the answer by the author, I think it'll be very helpful.

Anyways, this is what you should probably do to load the actual ui.bootstrap and influences.services modules in the Test file:

'use strict';

/* jasmine specs for controllers go here */

describe('influences controllers', function() {
  // Change is here. Notice how the dependencies of the influences.controllers
  // module are specified separately in another beforeEach directive
  beforeEach(module('influences.controllers'));
  beforeEach(function() {
    module('ui.bootstrap');
    module('influences.services');
  });

  describe('IndividualCtrl', function(){

    var scope, ctrl, service, $httpBackend;

    beforeEach(inject(function(_$httpBackend_, $rootScope, $controller, Api_sunlight_get) {
      console.log('*** IN INJECT!!***: ', Api_sunlight_get);
      $httpBackend = _$httpBackend_;
      // ignore for now... this is an example of how I might implement this later
      // $httpBackend.expectGET('data/products.json').
      //     respond([{name: 'Celeri'}, {name: 'Panais'}]);

      scope = $rootScope.$new();
      service = Api_sunlight_get;
      ctrl = $controller('IndividualCtrl', {$scope: scope, Api_sunlight_get: service
      });
    }));

    it('should create "products" model with 2 products fetched from xhr', function() {
      console.log('*** IN TEST!!***: ', scope);
      expect(scope.zip).toEqual(12345);
    });
  });
});

If you want to mock out those 2 dependencies, you might want to consult the link above, which I will repeat here for convenience: Mocking Angular module dependencies in Jasmine unit tests

Below are all the files for the scaled down version I tried if you are interested. Just put them in the same folder. You will require karma, angular.js, phantomjs and angular-mocks to run the test. angular-mocks can be obtained from the angular-seed project here.

To run the test, just:

karma start karma.test.conf.js

Apologies for putting all the files here, since I don't really know a good place to put multiple files like this.

ctrl.js:

angular.module('influences.controllers', ['influences.services'])
    .controller('IndividualCtrl', [
        '$scope', 
        'Api_sunlight_get',
        function($scope, Api_sunlight_get) {
            $scope.zip = $scope.zip || 12345;
        }
    ])

service.js:

angular.module('influences.services', [])
    .factory('Api_sunlight_get', [
        '$http',
        function($http) {
            console.log('Api_sunlight_get factory called');
        }
    ])

test.spec.js:

describe('influences controllers', function() {
    beforeEach(module('influences.controllers'));
    beforeEach(function() {
        module('influences.services');
    });
    describe('IndividualCtrl', function() {
        var scope
          , ctrl
          , service
          , $httpBackend;
        beforeEach(inject(function(_$httpBackend_, $rootScope, $controller, Api_sunlight_get) {
            console.log('*** IN INJECT!! ***');
            $httpBackend = _$httpBackend_;
            scope = $rootScope.$new();
            service = Api_sunlight_get;
            ctrl = $controller('IndividualCtrl', {
                $scope: scope,
                Api_sunlight_get: service
            });
        }));

        it('should set the correct zip value', function() {
            expect(scope.zip).toBe(12345);
        });
    });
});

karma.test.conf.js:

// Karma configuration
// Generated on Tue Jul 02 2013 11:23:33 GMT+0800 (SGT)


// base path, that will be used to resolve files and exclude
basePath = './';


// list of files / patterns to load in the browser
files = [
  JASMINE,
  JASMINE_ADAPTER,
  'angular.min.js',
  'angular-mocks.js',
  'service.js',
  'ctrl.js',
  'test.spec.js'
];


// list of files to exclude
exclude = [
];


// test results reporter to use
// possible values: 'dots', 'progress', 'junit'
reporters = ['progress'];

hostname = '127.0.0.1';

// web server port
port = 9876;


// cli runner port
runnerPort = 9100;


// enable / disable colors in the output (reporters and logs)
colors = true;


// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = LOG_INFO;


// enable / disable watching file and executing tests whenever any file changes
autoWatch = true;


// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers = ['PhantomJS'];


// If browser does not capture in given timeout [ms], kill it
captureTimeout = 60000;


// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = true;
Community
  • 1
  • 1
yanhan
  • 3,507
  • 3
  • 28
  • 38