2

I've seen this answered but none of the solutions are working for me. I'm getting an error Error: [ng:areq] Argument 'fn' is not a function, got undefined which is causing my test to fail. What's the correct way to inject a factory into a test spec that isn't a stubbed factory.

login module

angular.module('loginModule', ['ui.bootstrap', 'permission', 'ui.router', 'coreModule', 'utilsModule']);

login controller

  (function(){
      'use strict';

      angular.module('loginModule')
      .controller('loginController', login);

      login.$inject = [
          '$log',
          '$uibModal',
          '$rootScope',
          'storageFactory',
          'loginFactory',
          '$state',
          'RoleStore',
          'PermissionStore',
          'vsmsCoreFactory'
      ];

      function login($log, $uibModal, $rootScope, storageFactory, loginFactory, $state, RoleStore, PermissionStore, vsmsCoreFactory) {

          /* jshint validthis: true */
          var vm = this;
          vm.loginUser = loginUser;
          vm.forgotPassword = forgotPassword;
          vm.errorCode = null;
          PermissionStore.clearStore();
          vsmsCoreFactory.getHashFunction()
            .then(function(response) {
                if(response) {
                  storageFactory.setHashFunction(response); // unknown provider
              }
            });

          function loginUser() {
          ...

login controller spec

describe('loginController', function() {

  var $controller;

  beforeEach(module('loginModule'));

  beforeEach(inject(function(_$controller_){
    $controller = _$controller_;
  }));

  describe('vm.loginUser', function() {
    it('should be defined', function() {
      var loginController = $controller('loginController');
      expect(loginController.loginUser).toBeDefined();
    });
  });

});

unit test files

'use strict';

var gulp = require('gulp');

var $ = require('gulp-load-plugins')();

var wiredep = require('wiredep');

var paths = gulp.paths;

function runTests (singleRun, done) {
  var bowerDeps = wiredep({
    directory: 'bower_components',
    exclude: ['bootstrap-sass-official'],
    dependencies: true,
    devDependencies: true
  });

  var testFiles = bowerDeps.js.concat([
    './src/components/scripts/ui-bootstrap-custom-tpls-2.1.3.js',
    './src/app/index.js',
    './src/{app,components}/**/*.module.js',
    './src/{app,components}/**/*.factory.js',
    './src/{app,components}/**/*.controller.js',
    './src/{app,components}/**/*.spec.js'
  ]);

  gulp.src(testFiles)
    .pipe($.karma({
      configFile: 'karma.conf.js',
      action: (singleRun)? 'run': 'watch'
    }))
    .on('error', function (err) {
      // Make sure failed tests cause gulp to exit non-zero
      throw err;
    });
}

gulp.task('test', function (done) { runTests(true /* singleRun */, done) });
gulp.task('test:auto', function (done) { runTests(false /* singleRun */, done) });

app

'use strict';

angular.module('fotaAdminPortal',
    [
    'ngAnimate',
    'ngCookies',
    'ngTouch',
    'ngSanitize',
    'ngResource',
    'ui.bootstrap',
    'ui.router',
    'ui.router.stateHelper',
    'pascalprecht.translate',
    'utilsModule',
    'loginModule',
    ...
    ])

log

Chrome 54.0.2840 (Mac OS X 10.11.6) loginController vm.loginUser should be defined FAILED Error: [$injector:unpr] Unknown provider: storageFactoryProvider <- storageFactory <- loginController

1 Answer 1

1

The fotaAdminPortal module isn't loaded for the test so vsmsCoreFactory never gets registered.

Also there's no need to manually pass services into a controller as the injector will automatically do this when you create it. Passing services manually is useful when you need to pass something that isn't a globally registered service ie $scope or mock a service for a single controller instance.

Atm the mocks for the factories are just an undefined variable which will cause errors when the logic depending on them tries to call the methods that don't exist. You'll need to properly stub any methods on these mocks. But leaving those aside for the moment your test should be something like:

describe('loginController', function() {

  var $controller;

  beforeEach(module('fotaAdminPortal'));

  beforeEach(inject(function(_$controller_){
    $controller = _$controller_;
  }));

  describe('vm.loginUser', function() {
      it('should be defined', function() {
        var loginController = $controller('loginController');
        expect(loginController.loginUser).toBeDefined();
      });
  });

});

If you want to load loginModule in isolation then you need to extract vsmsCoreFactory into another module and add that as a dependency:

angular.module('vsmsCore', [])
  .factory('vsmsCoreFactory', function($http, env, $log, storageFactory) {

  });

angular.module('loginModule', ['ui.bootstrap', 'permission', 'ui.router', 'vsmsCore']);

Then you can do beforeEach(module('loginModule')) instead

Sign up to request clarification or add additional context in comments.

3 Comments

fotaAdminPortal is the main app module, which is loaded by karma ('./src/app/index.js'), as well as vsmsCoreFactory ('./src/{app,components}/**/*.factory.js'), as shown in unit test files. The karma logs also show that both the app file and the factory are loaded. loginModule is a dependency of fotaAdminPortal.
The file is loaded, but unless you boot up the module with module(..) then it's never registered. Problem is loginModule depends on services (vsmsCoreFactory) from the parent module so it's tightly coupled and can't be created in isolation. Either you boot the whole app or you extract the service into a separate module and add that as a dependency to loginModule
Ok, makes sense. I created a coreModule for the factory and added that as a dependency to loginModule, which fixed the issue of vsmsCoreFactory being undefined. Now, I'm seeing Unknown provider: storageFactoryProvider <- storageFactory <- loginController. storageFactory belongs to the utilsModule, which I've also defined as a loginModule dependency.

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.