Как протестировать контроллер Angular с функцией, которая выполняет вызов AJAX, не издеваясь над вызовом AJAX?

У меня есть функция в контроллере, которая вызывает службу, которая, в свою очередь, вызывает другую службу, которая выполняет вызов AJAX и возвращает обещание. Проще говоря:

MainController
    call = function(){
        //Handle the promise here, update some configs and stuff
        someService.call().then(success, failure);
    }

SomeService
    return {
        call: function(){
            return SomeOtherService.call();
        }
    }

SomeOtherService
    return {
        call: function(){
            return $http.get(someUrl);
        }          
    }

Как проверить функцию вызова основного контроллера без фактического вызова AJAX? Я, очевидно, буду тестировать услуги отдельно.

Обновление: Итак, я внес изменения в соответствии с ответом ниже, и вот что я получаю:

angular.module('myApp')
.controller('LoginCtrl', ['$scope', '$location', 'AuthService', '$state', function ($scope, $location, AuthService, $state) {
    $scope.user     = {};
    $scope.login    = function(user) {
        $scope.user = user;

        //This login function in turn calls the 'authenticate' from another service 'Ajax' and the promise is handled here.
        AuthService.login($scope.user)
            .then(function() {
                $scope.loginStatus = {
                    message     : 'Login successful',
                    alertClass  : 'success'
                };
                $state.go('app.dashboard');
            }, function() {
                $scope.loginStatus = {
                    message     : 'Unauthorized access.',
                    alertClass  : 'danger'
                };
            });
        };

    $scope.closeAlert = function() {
        $scope.loginStatus = false;
    };
}]);

PhantomJS 1.9.7 (Linux) Controller: LoginCtrl should call login FAILED
TypeError: 'undefined' is not an object (evaluating 'AuthService.login($scope.user)
                .then')
    at /home/rutwickg/Projects/workspace/myProject/app/scripts/controllers/login.js:15
    at /home/rutwickg/Projects/workspace/myProject/test/spec/controllers/login.js:34
    at /home/rutwickg/Projects/workspace/myProject/node_modules/karma-jasmine/lib/boot.js:117
    at /home/rutwickg/Projects/workspace/myProject/node_modules/karma-jasmine/lib/adapter.js:171
    at http://localhost:8080/karma.js:189
    at http://localhost:8080/context.html:145

Вот как выглядит мой текущий код:

'use strict';

describe('Controller: LoginCtrl', function() {

// load the controller's module
beforeEach(module('sseFeApp'));

var LoginCtrl,
    scope,
    authMock,
    ajaxMock,
    q;

// Initialize the controller and a mock scope
beforeEach( inject(function($controller, $rootScope, _AuthService_, _Ajax_, $q) {
        authMock = _AuthService_;
        ajaxMock = _Ajax_;
        q = $q;

        scope = $rootScope.$new();
        LoginCtrl = $controller('LoginCtrl', {
            $scope: scope
        });
    }));

it('should call login', function() {
        var deferred = q.defer();
        spyOn( authMock, 'login').and.returnValue(deferred.promise);
        $scope.login({username: 'Sample', password: 'Sample'});
        deferred.reject({});
        expect( authMock.login ).toHaveBeenCalled();

        //expect( ajaxMock.login).toHaveBeenCalled(); //Expect the second service call
    });
});

Обновление: в соответствии с новым синтаксисом должно быть and.returnValue(deferred.promise). Работает!


person Rutwick Gangurde    schedule 21.10.2014    source источник
comment
Не могли бы вы также предоставить фактический контроллер?   -  person m.brand    schedule 22.10.2014
comment
Проверьте это сейчас, пожалуйста, я также добавил контроллер.   -  person Rutwick Gangurde    schedule 22.10.2014
comment
Проверьте мой ответ. Я добавил $q/deferred-part.   -  person m.brand    schedule 22.10.2014


Ответы (1)


Вы просто вводите свой собственный сервис и шпион в функцию, которую вы вызываете.

describe( 'Controller: myCtrl', function () {

  var myService,
      ...;

  beforeEach( inject( function ( _myService_ ) {
    ...
    myService = _myService_;
    ...
  } ) );

  it( 'should call myService.call', inject(function ($q)
  {
    var deferred = $q.defer();
    spyOn( myService, 'call' ).andReturn(deferred.promise);
    // .and.returnValue() for jasmine 2.0
    $scope.myControllerFn();
    deferred.resolve( {} ); // for success 
    // deferred.reject() --> for error
    $scope.digest(); // do this if you want success/error to be executed.
    expect( myService.call ).toHaveBeenCalled();
  } );
} );
person m.brand    schedule 21.10.2014
comment
Пожалуйста, позвольте мне попробовать это. - person Rutwick Gangurde; 21.10.2014
comment
Это дает мне ошибку «не определено как объект». Кроме того, этот код проверяет только один уровень обслуживания. Мой контроллер вызывает службу, которая, в свою очередь, вызывает другую службу. - person Rutwick Gangurde; 22.10.2014
comment
Где происходит ошибка? Что на самом деле не определено? Второй уровень работает почти так же. Вы вводите службу, которую хотите имитировать, и шпионите за функцией, которую вы ожидаете вызвать. - person m.brand; 22.10.2014
comment
Я обновил описание своего вопроса. Пожалуйста, проверьте. - person Rutwick Gangurde; 22.10.2014
comment
Теперь он говорит, что $q.defer() не определено! - person Rutwick Gangurde; 22.10.2014
comment
это, вероятно, потому, что вы не вводили его. - person m.brand; 23.10.2014
comment
Ой, подождите, я думаю, это связано с синтаксисом. Должно быть spyOn(...).and.returnValue();. - person Rutwick Gangurde; 27.10.2014
comment
Большое спасибо @m.brand, это работает! Всего одно сомнение. Как проверить, что содержит переменная loginStatus в зависимости от того, было ли обещание отклонено или разрешено? Должен ли я задать новый вопрос для этого? - person Rutwick Gangurde; 27.10.2014
comment
Что ж, если вы выполните $scope.$digest() после разрешения или отклонения, будет вызван успех или ошибка. Итак, если вы хотите проверить свою успешную часть, вам нужно разрешить() отложенную, а если вы хотите проверить свою ошибочную часть, вам нужно отклонить() свою отложенную. - person m.brand; 28.10.2014
comment
Это решает проблему, которая у меня могла возникнуть позже, но текущая проблема, с которой я столкнулся, заключается в том, что мне нужно протестировать функцию, которая вызывает функцию, которая запускает запрос AJAX. Это не совсем подходит для моей ситуации. Если у вас есть какие-либо предложения, у меня есть вопрос, открытый по адресу stackoverflow.com/questions/31975928/. Спасибо, C§ - person CSS; 13.08.2015