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!")
  ]

1 Answer 1

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;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.