0

I have an AngularJS controller test that works just fine UNTIL I add a certain module. Here's the error I'm getting:

TypeError: 'undefined' is not a function (evaluating 'this.handlePostMessage.bind(this)')

The module in question is ng-token-auth. Here's my app's config file:

'use strict';

/**
 * @ngdoc overview
 * @name lunchHubApp
 * @description
 * # lunchHubApp
 *
 * Main module of the application.
 */
var app = angular.module('lunchHubApp', [
  'ngAnimate',
  'ngCookies',
  'ngResource',
  'ngRoute',
  'ngSanitize',
  'ngTouch',
  'rails',
  'ng-token-auth' // <------------ IT WORKS WITHOUT THIS
]);

app.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
  $locationProvider.html5Mode(true);
  $routeProvider
    .when('/', {
      templateUrl: 'views/main.html',
      controller: 'MainCtrl'
    })
    .when('/today', {
      templateUrl: 'views/announcements.html',
      controller: 'AnnouncementsCtrl',
      resolve: {
        auth: ['$auth', function($auth) {
          return $auth.validateUser();
        }]
      }
    })
    .when('/groups', {
      templateUrl: 'views/groups.html',
      controller: 'GroupsCtrl'
    })
    .when('/sign_in', {
      templateUrl: 'views/user_sessions/new.html',
      controller: 'UserSessionsCtrl'
    })
    .when('/sign_up', {
      templateUrl: 'views/user_registrations/new.html',
      controller: 'UserRegistrationsCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });
}]);

app.config(['$authProvider', function($authProvider) {
  $authProvider.configure({
    apiUrl: '/api'
  });
}]);

app.factory('Announcement', ['railsResourceFactory', function (railsResourceFactory) {
  return railsResourceFactory({ url: '/api/announcements', name: 'announcement' });
}]);

app.factory('Group', ['railsResourceFactory', function (railsResourceFactory) {
  return railsResourceFactory({ url: '/api/groups', name: 'group' });
}]);

app.run(['$rootScope', '$location', function($rootScope, $location) {
  $rootScope.$on('auth:login-success', function() {
    $location.path('/today');
  });
  $rootScope.$on('auth:logout-success', function() {
    $location.path('/sign_in');
  });
}]);

This is what my test looks like:

'use strict';

describe('AnnouncementsCtrl', function() {
  beforeEach(module('lunchHubApp'));

  it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
    var scope = $rootScope.$new(),
        ctrl = $controller('AnnouncementsCtrl', { $scope: scope });

    expect(scope.announcements).toEqual([]);
  }));
});

And this is my Karma config:

// Karma configuration
// http://karma-runner.github.io/0.12/config/configuration-file.html
// Generated on 2014-07-18 using
// generator-karma 0.8.3

module.exports = function(config) {
  'use strict';

  config.set({
    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

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

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'bower_components/angular-animate/angular-animate.js',
      'bower_components/angular-cookies/angular-cookies.js',
      'bower_components/angular-resource/angular-resource.js',
      'bower_components/angular-route/angular-route.js',
      'bower_components/angular-sanitize/angular-sanitize.js',
      'bower_components/angular-touch/angular-touch.js',
      'bower_components/angular-route/angular-route.js',
      'bower_components/angularjs-rails-resource/angularjs-rails-resource.js',
      'bower_components/ng-token-auth/dist/ng-token-auth.js',
      'app/scripts/app.js',
      'app/scripts/controllers/announcements.js',
      'test/spec/controllers/announcements.spec.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

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

    // Which plugins to enable
    plugins: [
      'karma-phantomjs-launcher',
      'karma-jasmine'
    ],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false,

    colors: true,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // Uncomment the following lines if you are using grunt's server to run the tests
    // proxies: {
    //   '/': 'http://localhost:9000/'
    // },
    // URL root prevent conflicts with the site root
    // urlRoot: '_karma_'
  });
};

Why am I getting such an error?

Jason Swett
  • 43,526
  • 67
  • 220
  • 351

1 Answers1

0

Try adding this as a module in your test also. The ngMocks module allows you to essentially create anonymous functions as modules. If your code that is executing is using functionality from this module, then you also have to load that module into the injector during your test:

describe('AnnouncementsCtrl', function() {
  beforeEach(module('lunchHubApp'));
  beforeEach(module('ng-token-auth');

  it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
    var scope = $rootScope.$new(),
        ctrl = $controller('AnnouncementsCtrl', { $scope: scope });

    expect(scope.announcements).toEqual([]);
  }));
});

Edit/notes by OP:

So in other words, I guess the answer is to mock the module rather than trying to get it to work. Fair enough. I was able to get my test to work with code very similar to this answer. First, I created a separate file to mock ng-token-auth:

angular.module('ng-token-auth', []).provider('$auth', function() {
  return {
    configure: function() {},
    $get: []
  };
});

Then I made sure to include that file in my Karma config. Now here is my new test:

'use strict';

describe('AnnouncementsCtrl', function() {
  beforeEach(module('lunchHubApp', 'ng-token-auth'));

  it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
    var scope = $rootScope.$new(),
        ctrl = $controller('AnnouncementsCtrl', { $scope: scope });

    expect(scope.announcements).toEqual([]);
  }));
});

That runs fine.

Jason Swett
  • 43,526
  • 67
  • 220
  • 351
sma
  • 9,449
  • 8
  • 51
  • 80
  • Hmm, that didn't seem to have any effect. – Jason Swett Oct 07 '14 at 21:43
  • Okay, you got me pretty close. I accepted your answer. This Q/A got me the rest of the way there: http://stackoverflow.com/questions/17554727/mocking-angular-module-dependencies-in-jasmine-unit-tests?rq=1 – Jason Swett Oct 07 '14 at 22:14