Как протестировать фабричный метод AngularJS, который возвращает обещание $ timeout с помощью $ http.get inside?

#javascript #angularjs #unit-testing #jasmine

#javascript #angularjs #модульное тестирование #jasmine

Вопрос:

Для определенных целей мне пришлось написать фабричный метод, который возвращает $timeout обещание, которое внутри него возвращает $http.get обещание.

Я хочу проверить, вызовет ли вызов моего фабричного метода $http.get с правильным поддельным URL ( mocked-get-path ).

Вот мой заводской код:

 (function() {
  'use strict';

  angular.module('MyApp', []);

  angular.module('MyApp')
    .constant("ROUTES", {
      get: "real-get-path"
    });

  angular.module('MyApp')
    .factory('MyFactory', ['$http', '$timeout', 'ROUTES', MyFactory]);

  function MyFactory($http, $timeout, ROUTES) {
    return {
      myGet: function(id) {
        var random = Math.random() * 1000;
        return $timeout(function () {
            return $http.get(ROUTES.get, {
              params: { id: id }
            })
              .then(function() {
                return response.data;
              });
        }, random);
      }
    };
  }
})();
  

И моя спецификация теста:

 describe('simple factory test', function() {
  var $http, $timeout, scope, MyFactory;

  var ROUTES = {
      get: 'mocked-get-path'
  };

  beforeEach(module('MyApp', function ($provide) {
      $provide.constant('ROUTES', ROUTES);
  }));

  beforeEach(module('MyApp'));

  beforeEach(inject(function(_$rootScope_, _$http_, _$timeout_, _MyFactory_) {
    scope = _$rootScope_.$new();
    $http = _$http_;
    $timeout = _$timeout_;
    MyFactory = _MyFactory_;
  }));

  it('should use ROUTES.get on method myGet', function(done) {
    spyOn(Math, "random").and.returnValue(0.01);

    MyFactory.myGet('elem1')
      .then(function(res) {
        expect($http.get).toHaveBeenCalledWith(ROUTES.get);
        done();
      });
    $timeout.flush();
  });
});
  

Вы можете видеть, что я пытался написать expect для $http.get внутри then , но это не сработало.

Я получаю эту ошибку от Jasmine:

Ошибка: Неожиданный запрос: ПОЛУЧИТЬ mocked-get-path?id=elem1

Я создал плунжер:https://plnkr.co/edit/Ia6Q6GvKZOkNU2B8GrO1

Что я делаю не так?

Ответ №1:

При тестировании $http вы захотите использовать $httpBackend.when

В вашем случае:

 it('should use ROUTES.get on method myGet', function(done) {
  spyOn(Math, "random").and.returnValue(0.01);
  spyOn($http, "get").and.callThrough();

  MyFactory.myGet('elem1')
    .then(function(res) {
      expect($http.get).toHaveBeenCalledWith(ROUTES.get, {params: {id: 'elem1'}});
      done();
    });

  $httpBackend
    .when('GET', "mocked-get-path?id=elem1")
    .respond(200, { foo: 'bar' });

  $timeout.flush(100);

  $timeout.verifyNoPendingTasks();
  $httpBackend.flush();

});
  

Это приведет к тому, что $httpBackend ваш запрос будет успешно завершен, что позволит вашему .then с ожидаемым выполнить.

Plunkr показывает решение

основы ngmock

Я надеюсь, что это поможет! Хорошего дня!

Комментарии:

1. Спасибо за ваш ответ, но я пробовал что-то подобное и не сработало. Пожалуйста, проверьте эту модификацию, которую я написал с вашим предложением: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh

2. Я решил это. В моем решении отсутствовал $ httpBackend. flush() после $timeout. flush()

3. Большое вам спасибо! Вот модификация: plnkr.co/edit/YvF3Lem8pYRmm0SpCke2

Ответ №2:

Один из подходов заключается в добавлении ожидаемых вызовов к httpBackend следующим образом:

 var  $httpBackend;
beforeEach(function () {
        inject(function ( _$httpBackend_ ) {
            $httpBackend = _$httpBackend_;
        }
  

….

 it('....', function(){
    //before the rest of the test
    $httpBackend.expectGET('url').respond("OK");

});
  

https://docs.angularjs.org/api/ngMock/service /$httpBackend

Итак, ваш код будет выглядеть следующим образом:

 describe('simple directive', function() {
  var $http, $timeout, scope, MyFactory, $httpBackend;

  var ROUTES = {
      get: 'mocked-get-path'
  };

  beforeEach(module('MyApp', function ($provide) {
      $provide.constant('ROUTES', ROUTES);
  }));

  beforeEach(module('MyApp'));

  beforeEach(inject(function(_$rootScope_, _$http_, _$timeout_, _MyFactory_, _$httpBackend_) {
    $httpBackend = _$httpBackend_;
    scope = _$rootScope_.$new();
    $http = _$http_;
    $timeout = _$timeout_;
    MyFactory = _MyFactory_;
  }));

  it('should use ROUTES.get on method myGet', function(done) {
    spyOn(Math, "random").and.returnValue(0.01);
    $httpBackend.expectGET("mocked-get-path?id=elem1").respond("OK");
    MyFactory.myGet('elem1')
      .then(function(res) {
        expect($http.get).toHaveBeenCalledWith(ROUTES.get);
        done();
      });
    $timeout.flush();
  });
});
  

Это приведет к передаче неожиданной ошибки GET. Но тогда есть еще одна проблема, связанная с неполучением вызова в течение периода ожидания.

И, при условии, что теперь вы ожидаете правильного вызова в $ httpBackend, вы можете упростить свой тестовый пример, удалив его асинхронный характер следующим образом:

(удалите paremeter done и упростите тестовый код)

 it('should use ROUTES.get on method myGet', function() {
    spyOn(Math, "random").and.returnValue(0.01);
    $httpBackend.expectGET("mocked-get-path?id=elem1").respond("OK");
    MyFactory.myGet('elem1');
    $timeout.flush();
  });
  

Комментарии:

1. Спасибо за ваш ответ, я попробовал то, что вы предложили, но это не сработало. Пожалуйста, проверьте эту модификацию, которую я написал с вашим предложением: plnkr.co/edit/b0hyazxaC5P3qKpLjqLm

2. Я приложил полный код, который устраняет неожиданную проблему с получением.

3. Еще раз спасибо. Теперь ошибка другая: «Ошибка: Тайм-аут — асинхронный обратный вызов не был вызван в течение времени ожидания, указанного jasmine. DEFAULT_TIMEOUT_INTERVAL.» Вот модификация: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh

4. смотрите мою последнюю правку. ваши условия тестирования теперь проверяются ожиданиями в httpBackend, поэтому это можно выполнить с помощью вызовов синхронизации.

5. Еще раз спасибо, но почему результат Jasmine сообщает «СПЕЦИФИКАЦИЯ НЕ ИМЕЕТ ОЖИДАНИЙ, следует использовать ROUTES.get для метода MyGet»? Обновленный плунжер: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh