Карма / Жасмин тест ионного применения, не впрыскивая контроллер

0

Я добавляю некоторые тесты к ионному приложению, используя Карму и Жасмин. В этот момент я пытаюсь провести тесты для углового контроллера входа. Однако, похоже, не выполняется выполнение инъекций, которые создают контроллер и издеваются над всеми внешними зависимостями. Вот код, который я использую:

login.controller.js

(function() {
  var app = angular.module("myApp");

  app.controller("LoginController", function($translate, $state, $ionicPopup, $localStorage, sessionService, userService) {
    var vm = this;

    vm.email = null;
    vm.password = null;

    vm.login = function() {
      $localStorage.email = vm.email;
      $localStorage.password = vm.password;

      sessionService.login().then(
        function(data) {
          if (userService.isAuthenticated()) {
            // User is logged, go to the next state
            $state.go("next_state");
          } else {
            // User is not logged (invalid credentials), show an alert message
            var alertPopup = $ionicPopup.alert({
              title: $translate.instant("LOGIN_FAIL_TITLE"),
              template: $translate.instant("LOGIN_FAIL_MESSAGE")
            });
          }
        }
      );
    }
  });
})();

login.controller.tests.js

describe("LoginController", function() {
  var controller,
      deferredLogin,
      translateMock,
      stateMock,
      ionicPopupMock,
      localStorageMock,
      sessionServiceMock,
      userServiceMock;

  beforeEach(module("myApp"));

  beforeEach(module(function($provide, $urlRouterProvider) {
    $provide.value("$ionicTemplateCache", function() {});
    $urlRouterProvider.deferIntercept();
  }));

  beforeEach(inject(function($controller, $q) {
    deferredLogin = $q.defer();

    sessionServiceMock = {
      login: jasmine.createSpy("login spy")
                    .and.returnValue(deferredLogin.promise)
    };

    translateMock = jasmine.createSpyObj("$translate spy", ["instant"]);

    stateMock = jasmine.createSpyObj("$state spy", ["go"]);

    ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);

    localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);

    userServiceMock = jasmine.createSpyObj("userService spy", ["isAuthenticated"]);

    controller = $controller("LoginController", {
      "$translate": translateMock,
      "$state": stateMock,
      "$ionicPopup": ionicPopupMock,
      "$localStorage": localStorageMock,
      "sessionService": sessionServiceMock,
      "userService": userServiceMock
    });
  }));

  describe("#login", function() {
    beforeEach(inject(function(_$rootScope_) {
      $rootScope = _$rootScope_;
      controller.email = "[email protected]";
      controller.password = "foobarfoo";
      controller.login();
    }));

    it("should call login on sessionService", function() {
      expect(sessionServiceMock.login).toHaveBeenCalledWith("[email protected]", "foobarfoo");
    });

    describe("when the login is executed", function() {
      it("if successful, should change state to next_state", function() {
        deferredLogin.resolve();
        $rootScope.$digest();
        expect(stateMock.go).toHaveBeenCalledWith("next_state");
      });

      it("if unsuccessful, should show a popup", function() {
        deferredLogin.reject();
        $rootScope.$digest();
        expect(ionicPopupMock.alert).toHaveBeenCalled();
      });
    });
  });
});

Я искал все места, которые пытались найти разные решения, но я всегда получаю те же результаты:

...
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login should call login on sessionService FAILED
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    TypeError: undefined is not an object (evaluating 'sessionServiceMock.login') in unit-tests/controllers/login.controller.tests.js (line 63)
    unit-tests/controllers/login.controller.tests.js:63:32
    loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if successful, should change state to capture_image FAILED
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    TypeError: undefined is not an object (evaluating 'deferredLogin.resolve') in unit-tests/controllers/login.controller.tests.js (line 68)
    unit-tests/controllers/login.controller.tests.js:68:22
    loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) LoginController #login when the login is executed if unsuccessful, should show a popup FAILED
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    forEach@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:13691:24
    loadModules@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17878:12
    createInjector@/home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17800:30
    workFn@/home/user/NSS/my-app/www/lib/angular-mocks/angular-mocks.js:2922:60
    loaded@http://localhost:9876/context.js:151:17
    /home/user/NSS/my-app/www/lib/ionic/js/ionic.bundle.js:17918:53
    TypeError: undefined is not an object (evaluating 'deferredLogin.reject') in unit-tests/controllers/login.controller.tests.js (line 74)
    unit-tests/controllers/login.controller.tests.js:74:22
    loaded@http://localhost:9876/context.js:151:17
...

Я не понимаю, что я делаю неправильно. Заранее спасибо.

ОБНОВИТЬ:

Как говорит Мэтью Грин, я изменил способ написания тестов. Теперь они следуют его рекомендациям, и я также добавил еще несколько инъекций, которые необходимы:

describe("LoginController", function() {
  var controller,
      stateMock,
      ionicPopupMock,
      localStorageMock,
      sessionServiceMockPromise,
      sessionServiceMock;

  // Load the application module
  beforeEach(module("myApp"));

  // Avoid trying to load all templates of the application
  beforeEach(module(function($provide, $urlRouterProvider) {
    $provide.value("$ionicTemplateCache", function() {});
    $urlRouterProvider.deferIntercept();
  }));

  // Avoid asynchronous loader of the translations (problem with the angular-translate-design)
  beforeEach(module(function($provide, $translateProvider) {
    $provide.factory("customLoader", function($q) {
      return function() {
        var deferred = $q.defer();
        deferred.resolve({});
        return deferred.promise;
      };
    });

    $translateProvider.useLoader("customLoader");
  }));

  // Instanciate and initialize the controller and mocks
  beforeEach(inject(function($controller, $q) {
    sessionServiceMockPromise = $q.defer();
    sessionServiceMock = jasmine.createSpyObj("sessionServiceMock", ["login"]);
    sessionServiceMock.login.and.callFake(function() {
      return sessionServiceMockPromise.promise;
    });

    stateMock = jasmine.createSpyObj("$state spy", ["go"]);

    ionicPopupMock = jasmine.createSpyObj("$ionicPopup spy", ["alert"]);

    localStorageMock = jasmine.createSpyObj("$localStorage spy", ["getItem"]);

    controller = $controller("LoginController", {
      "$state": stateMock,
      "$ionicPopup": ionicPopupMock,
      "$localStorage": localStorageMock,
      "sessionService": sessionServiceMock
    });
  }));

  describe("#login", function() {
    beforeEach(inject(function(_$rootScope_) {
      $rootScope = _$rootScope_;
      controller.login();
      sessionServiceMockPromise.resolve();
    }));

    it("should call login on sessionService", function() {
      expect(sessionServiceMock.login).toHaveBeenCalledWith();
    });

    describe("when the login is executed", function() {
      it("if successfull, should change state to capture_image", inject(function(userService) {
        spyOn(userService, "isAuthenticated").and.callFake(function() { return true; });
        $rootScope.$digest();
        expect(stateMock.go).toHaveBeenCalledWith("nextState");
      }));

      it("should show a popup", inject(function(userService) {
        spyOn(userService, "isAuthenticated").and.callFake(function() { return false; });
        $rootScope.$digest();
        expect(ionicPopupMock.alert).toHaveBeenCalled();
      }));
    });
  });
});
  • 0
    undefined is not an object здесь не имеет смысла. Phantomjs имеет тенденцию глотать сообщения об ошибках. Попробуйте запустить его в другом браузере (Chrome) и проверьте ошибки.
Теги:
ionic-framework
karma-jasmine
jasmine

1 ответ

1
Лучший ответ

Похоже, ваш sessionService.login() - это функция, которая должна вернуть обещание. В этом случае я вижу несколько проблем с тем, что у вас есть.

Во-первых, я не знаю, почему вы решили настроить эту услугу, отличную от остальных. Скорее всего, это могут быть некоторые из ваших проблем. Если вы настроите его так же, как другие, это будет выглядеть примерно так.

var sessionServiceMockPromise = $q.defer();
sessionServiceMock = jasmine.createSpyObj('sessionServiceMock', ['login']);
sessionServiceMock.login.and.callFake(function() {
    return sessionServiceMockPromise.promise;
});

В этом случае мы устанавливаем, что логин - это функция, которая возвращает обещание. С sessionServiceMockPromise вы можете теперь вызвать sessionServiceMockPromise.resolve([data]) (или reject() на то пошло) в ваших тестах, чтобы увидеть результат вашего обещания.

Во-вторых, ваш тест говорит, что он ожидает, что логин будет вызван двумя переменными. Нет, где в вашем коде для службы или теста я вижу, что это функция, которая принимает переменные. Лучшим тестом может быть только то, что он вызывается или что состояние настроено на то, что вы ожидаете.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню