Тестирование AngularJS и $http

Итак, я пытаюсь понять, как писать модульные тесты для моего углового контроллера. Я использую карму в качестве бегуна. Я смог написать 1 успешный тест, но каждый раз, когда я пытаюсь написать еще один тест, он кричит на меня о неожиданных вызовах и тому подобном.

Вот мой контроллер, который я пытаюсь проверить.

(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {

    //"Global Variables"
    var vm = this;
    vm.success = false;
    vm.repos = [];

    //"Global Functions"
    vm.addRepository = addRepository;
    vm.listRepos = listRepos;

    //Anything that needs to be instantiated on page load goes in the init
    function init() {
        listRepos();
    }
    init();

    // Add a repository
    function addRepository(repoUrl) {
        $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
            vm.success = true;
            vm.addedRepo = vm.repoUrl;
            vm.repoUrl = '';
            listRepos();
        });
    }
    //Lists all repos
    function listRepos() {
        $http.get('/api/repo').then( function (response){
            vm.repos = response.data;

        });
    }
});
}(window.angular));

Итак, у меня есть тест, написанный для listRepos(). Это происходит следующим образом

describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));

beforeEach(inject(function($rootScope, $httpBackend, $controller) {
    httpBackend = $httpBackend;
    scope = $rootScope.$new();
    createController = function() {
        return $controller('dashboardCtrl', {
            '$scope': scope
        });
    };
}));

afterEach(function() {
    httpBackend.verifyNoOutstandingExpectation();
    httpBackend.verifyNoOutstandingRequest();
});

it('should call listRepos and return all repos from the database', function() {
    var controller = createController();
    var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];

    httpBackend.expect('GET', '/api/repo')
        .respond(expectedResponse);
    httpBackend.flush();

    scope.$apply(function() {
        scope.listRepos;
    });

    expect(controller.repos).toEqual(expectedResponse);
});

Это работает, и тест проходит. Теперь моя проблема в том, что я хочу написать еще один тест для проверки другой функции, которая вызывает новую конечную точку API.

Это тест, который я пытаюсь написать для addRepository.

 it('should addRepository to the database', function() {
        var controller = createController();
        var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";

        httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
        httpBackend.flush();

        scope.$apply(function() {
            scope.addRepository(givenURL);
        });

        expect(controller.success).toBe(true);
        expect(controller.listRepos).toHaveBeenCalled();
    });

Ошибка, которую я получаю, когда добавляю этот тест в спецификацию:

Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
    at $httpBackend 
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest

Пример, с которым я работаю: on-fire/" rel="nofollow">это здесь

Любые предложения или советы приветствуются!

ОБНОВЛЕНИЕ:

Итак, я изменил свою функцию, чтобы вернуть обещание из $http.post,

Я переписал свой второй тест, а также завернул свой первый тест в блок описания, описывающий функцию, которую он пытается протестировать.

Со следующим:

describe('addRepository', function () {

    it('should addRepository to the database', function () {
        var controller = createController();
        var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";

        httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');

        scope.$apply(function () {
            scope.addRepository(givenURL);
        });
        httpBackend.flush();

        expect(controller.success).toBe(true);
    });
    it('should call listRepos', function() {
        var controller = createController();
        httpBackend.expect('GET', '/api/repo').respond('success');

        controller.controller().then(function (result) {
            expect(controller.listRepos).toHaveBeenCalled();

        });
        httpBackend.flush();
    });
});

Я все еще получаю сообщение об ошибке:

Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
    at $httpBackend 
Error: [$rootScope:inprog] $digest already in progress

но и

TypeError: 'undefined' is not a function (evaluating 'controller.controller()') 
Error: Unflushed requests: 1

который показывает, что 2 теста не пройдены.


person CoffeePeddlerIntern    schedule 03.02.2016    source источник


Ответы (1)


flush должен идти после вызова функции. Я бы также изменил функцию, чтобы вернуть обещание из $http.post:

    // Add a repository
    function addRepository(repoUrl) {
        return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
            vm.success = true;
            vm.addedRepo = vm.repoUrl;
            vm.repoUrl = '';
            listRepos();
        });
    }

И затем в тесте вы можете вызвать его и проверить часть успеха:

ИЗМЕНИТЬ

Я поменял controller.controller() на то, что у вас есть.

it('should call listRepos', function() {
    // Your setup
    ctrl.addRepository().then(function(result) {
        expect(ctrl.listRepos).toHaveBeenCalled();
    });
});

ИЗМЕНИТЬ 2

Я эмулировал, насколько мог, ваш код и тесты, которые я пишу для кода:

(function () {
  'use strict';

  angular
    .module('myApp')
    .controller('DashboardController',DashboardController);

    DashboardController.$inject = ['$http'];

    function DashboardController($http) {

      var vm = this;
      vm.success = false;
      vm.repos = [];

      vm.addRepository = addRepository;
      vm.listRepos = listRepos;

      init();

      // Anything that needs to be instantiated on page load goes in the init
      function init() {
        vm.listRepos();
      } 

      // Add a repository
      function addRepository(repoUrl) {
        return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
          vm.success = true;
          vm.addedRepo = vm.repoUrl;
          vm.repoUrl = '';
          vm.listRepos();
        });
      }
      // Lists all repos
      function listRepos() {
        return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
          vm.repos = response.data;
        });
      }
    };
}());

Здесь я использую онлайн-API JSONPlaceholder для имитации HTTP-вызовов, поскольку я, очевидно, не могу попасть в то, на что вы указываете. И для теста (который все проходят):

(function() {
    'use strict';

    fdescribe('DashBoardController', function() {
        var $rootScope, scope, ctrl, $httpBackend;
        beforeEach(module('myApp'));

        beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
            $rootScope      = _$rootScope_; 
            scope           = $rootScope.$new();
            $httpBackend    =_$httpBackend_;

            ctrl = $controller('DashBoardController',{
                $scope: scope
            });
        }));

        beforeEach(function() {
            // Setup spies
            spyOn(ctrl,'listRepos');
        });

        describe('controller', function() {
            it('should be defined', function() {
                expect(ctrl).toBeDefined();
            });
            it('should initialize variables', function() {
                expect(ctrl.success).toBe(false);
                expect(ctrl.repos.length).toBe(0);
            });
        });

        describe('init', function() {

            it('should call listRepos', function() {
                $httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
                    .respond({success: '202'});

                $httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
                    .respond({success: '202'});

                ctrl.addRepository().then(function(result) {
                    expect(ctrl.success).toBe(true);
                    expect(ctrl.repoUrl).toBe('');
                    expect(ctrl.listRepos).toHaveBeenCalled();
                });

                $httpBackend.flush();
            });
        });
    });

}()); 
person Katana24    schedule 03.02.2016
comment
Я пробовал это, посмотрите мое обновление о том, как я его реализовал, и о возникающих ошибках. - person CoffeePeddlerIntern; 03.02.2016
comment
Мне удалось избавиться от ошибки неожиданного вызова. теперь я получаю TypeError: 'undefined' не является функцией (оценка 'controller.controller()') - person CoffeePeddlerIntern; 03.02.2016
comment
Перестаньте вызывать $scope.$appy(), который вам не нужен, — это избавит вас от одной ошибки. Почему вы вызываете controller.controller() - я знаю, что поместил его в свой код, но это был просто пример, хотя и с плохим названием. Смотрите мое редактирование - person Katana24; 03.02.2016
comment
TypeError: 'undefined' не является объектом (оценка 'controller.addRepository(givenURL).then') Ошибка: невыполненные запросы: 1 - person CoffeePeddlerIntern; 03.02.2016
comment
Хм, дайте мне минутку, чтобы подражать этому, это начинает меня раздражать. - person Katana24; 03.02.2016
comment
Нет проблем и не торопитесь. Спасибо за помощь для далеко. Я чувствую, что рисую пальцем в этот момент - person CoffeePeddlerIntern; 03.02.2016
comment
Просто хотел сказать, что Ив пробовал то же, что и ты, и у меня это не сработало, но не беспокойся об этом. Вы и так сделали достаточно, и я очень ценю это. Я предполагаю, как настроен мой угловой проект. Что я получаю, когда я попробовал ваше предложение, это - person CoffeePeddlerIntern; 05.02.2016
comment
PhantomJS 1.9.8 (Mac OS X 0.0.0) dashboardCtrl controller should be defined FAILED Error: listRepos() method does not exist PhantomJS 1.9.8 (Mac OS X 0.0.0) dashboardCtrl controller should initialize variables FAILED Error: listRepos() method does not exist PhantomJS 1.9.8 (Mac OS X 0.0.0) dashboardCtrl init should call listRepos FAILED Error: listRepos() method does not exist TypeError: 'undefined' is not an object (evaluating 'ctrl.addRepository().then') Спасибо за помощь. Продолжаю рисовать пальчиками. Это сводит меня с ума. - person CoffeePeddlerIntern; 05.02.2016
comment
Я вернусь и обновлю все, когда/если я заставлю это работать, чтобы сообщество могло извлечь выгоду из решения. Мне кажется, что есть несколько способов реализовать такие вещи, и это очень сложно. - person CoffeePeddlerIntern; 05.02.2016
comment
Вау, я нашел проблему! Это было в моем karam.conf.js. Я использую grunt runner, чтобы объединить все мои файлы js в 1. Карам запускал мой concat И мой обычный файл, что приводило к его беспорядку. Я добавил свои объединенные файлы в исключение karam, и это сработало. Большое спасибо за помощь. Извините за разочарование! - person CoffeePeddlerIntern; 05.02.2016
comment
@CoffeePeddlerIntern Рад, что вы нашли решение :) - person Katana24; 05.02.2016