PatrickJS / NG6-starter

:ng: An AngularJS Starter repo for AngularJS + ES6 + Webpack
https://angularclass.github.io/NG6-starter
Apache License 2.0
1.91k stars 1.35k forks source link

Unit Test using Karma for the controller with Service #146

Open kyleevert opened 8 years ago

kyleevert commented 8 years ago

Thank you for the great material. But I have a question for the unit test.

I have created one angularjs service and tried to inject in my teams.spec.js for unit test. But I get the error like this. Error: [$injector:unpr] unknown provider : EnvServiceProvider <- EnvService ...

Here is the source code.

import TeamsModule from './teams'
import TeamsController from './teams.controller';
import TeamsComponent from './teams.component';
import TeamsTemplate from './teams.html';
import Services from '../../services/services.js'

import 'ngstorage';
import env from '../../services/env.js'

describe('Teams', () => {
  let $rootScope, makeController, $localStorage, EnvService, $http;

  beforeEach(window.module(TeamsModule.name));
  beforeEach(window.module('ngStorage'));
  beforeEach(window.module(Services.name));

  // come out the error when injecting
  beforeEach(inject((_$rootScope_, _$http_, _$localStorage_, _EnvService_) => {
    $rootScope = _$rootScope_;
    $http = _$http_;
    EnvService = _EnvService_;
    makeController = () => {
      return new TeamsController();
    };
  }));

  describe('Module', () => {
    // top-level specs: i.e., routes, injection, naming
  });

  describe('Controller', () => {
    // controller specs
    it('has a name property [REMOVE]', () => { // erase if removing this.name from the controller
      let controller = makeController();
      expect(controller).to.have.property('name');
    });
  });

  describe('Template', () => {
    // template specs
    // tip: use regex to ensure correct bindings are used e.g., {{  }}
    it('has name in template [REMOVE]', () => {
      // expect(TeamsTemplate).to.match(/{{\s?vm\.name\s?}}/g);
    });
  });

  describe('Component', () => {
      // component/directive specs
      let component = TeamsComponent;

      it('includes the intended template',() => {
        expect(component.template).to.equal(TeamsTemplate);
      });

      it('uses `controllerAs` syntax', () => {
        expect(component).to.have.property('controllerAs');
      });

      it('invokes the right controller', () => {
        expect(component.controller).to.equal(TeamsController);
      });
  });
});
//---------- ...\client\app\services\services.js ----------
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import 'ngstorage';

import env from './env';
import security from './security';
import auth from './auth';
import events from './events';
import teams from './teams';
import companies from './companies';
import profile from './profile';
import common from './common';

export default angular
  .module('app.services', [])
  .service({
    env,
    security,
    auth,
    events,
    teams,
    companies,
    profile,
    common,
  })

---------- ...\client\app\services\env.js ----------
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import 'ngstorage';

export default class EnvService {
  constructor($http, $localStorage, $state, $q) {
    this.$http = $http;
    this.$localStorage = $localStorage;
    this.$state = $state;
    this.$q = $q;

  }

  getAPIEndPoint() {
    // return 'https://dev.int.jobbleapp.com/jobbleapi/';
    return 'https://jbl-qa-api.int.jobbleapp.com/jobbleapi/';
  }

  getTransActionFee() {
    return 0.05;
  }

}

EnvService.$inject = ['$http', '$localStorage', '$state', '$q'];

Could you let me know what the problem is?

I am really appreciate if you provide a unit test example with injecting a custom angular service.

Thanks

ttbarnes commented 8 years ago

I came across the same issue. You need to include it in the makeController function, like this:

makeController = () => {
  return new myController(myService);
};
samithaf commented 8 years ago

Just use the component controller for unit tests. I have updated this repo as well. It will make things like this pretty easy

ttbarnes commented 8 years ago

@samithaf ? The code above is using the component controller. Also, I don't understand what you have updated, there's no recent commits from you.

samithaf commented 8 years ago

@ttbarnes I can't see above code is using component controller. I have updated this repo to use component controller recetly. Here is my commit, https://github.com/AngularClass/NG6-starter/commit/1e861070db71ceb6a0ceffa73353ba24e15cfaec

ttbarnes commented 8 years ago

@samithaf Sorry if i'm misunderstanding something, but I can't see how this is related. This issue is about injecting a service into a controller, and unit testing it.

In fact, I can't see an example of a service in this repo, nor an example of the above issue. I can do a PR for these things.

pkishino commented 8 years ago

why are you injecting the service into your spec for the teamModule?? Ive created a simple service (for firebase initial config) that I have a separate spec for, my modules that use it have it injected into the controller component but I don't have it in the spec for that component.

pkishino commented 8 years ago

Oh and what @samithaf is referring to I think is that his commit to use component controllers simplifies the spec quite a bit.

samithaf commented 8 years ago

@pziegler @ttbarnes yup. as per my original comment, i was suggesting to use Angular component controller for unit tests. Component controller facilitate to write unit tests with ease! Also you can mock the entire service easily as follows.

describe('Controller', () => {
    beforeEach(() => {
      // mock login service
      const mockLoginService = () => {
        const service = {
          login: (form) => {
            const deferred = $q.defer();
            if (form.username === 'david') {
              deferred.resolve();
            } else {
              deferred.reject({ status: 403, reason: 'invalid user' });
            }

            return deferred.promise;
          },

          invalidateSession: () => {},
        };

        return service;
      };

      // mock user service
      const mockUserService = () => {
        const service = {
          fetchProfile: () => {
            const deferred = $q.defer();
            deferred.resolve();
            return deferred.promise;
          },
        };

        return service;
      };

      // use $componentController helper to init the controller
      // https://docs.angularjs.org/api/ngMock/service/$componentController
      controller = $componentController('login', {
        $q,
        $scope: $rootScope.$new(),
        loginService: mockLoginService(),
        userService: mockUserService(),
      });
    });
ttbarnes commented 8 years ago

@pziegler @samithaf Yes, the confusion has been that @calemyers723 wasn't sure how to inject a service into a unit test. Mocking a service is great, but you can also inject and mock, for example some service methods.

I think it's worth putting in an example of injecting a service, with or without with mocking.