Angular.js: ngMockE2E $ httpBackend "passThrough ()" не работает

Созданный на 2 окт. 2012  ·  68Комментарии  ·  Источник: angular/angular.js

Привет, я пытаюсь настроить какой-то тест, который действительно попадет в бэкэнд, но я не могу заставить это работать:

$ httpBackend.whenPOST (/. /). passThrough ()$ httpBackend.whenGET (/. /). passThrough ()

Но я получаю сообщение об ошибке: «POST / some / url Больше запросов не ожидается».

Я все еще могу использовать .whenGET (). Reply () и его варианты для имитации запросов. Я что-то не так делаю с passThrough ()?

Lots of comments scenario runner high confusing bug

Самый полезный комментарий

Хорошо, есть обходной путь, но вы не сможете иметь оба одновременно (модульный тест ожидает некоторый вызов, e2e просто отвечает поддельным или проходящим). Вот как вставить $ httpBackend из e2e в модульный тест

//be careful order is important
describe('inject e2e $httpBackend', function () {

  it('here the injection', function() {
    var $httpBackend;

    //instantiate your modules
    angular.mock.module('ngMockE2E', 'openstack.keystone.v2')

    angular.mock.module(function ($provide) {

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ng'])
      .invoke(function($httpBackend) {
        $provide.value('$delegate', $httpBackend);
      });

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ngMockE2E'])
      .invoke(['$httpBackend', function(_$httpBackend_){
        $httpBackend = _$httpBackend_;
      }]);

      $provide.value('$httpBackend', $httpBackend);
    });

   //and then it works
   $httpBackend.when('POST', '/identity/tokens').passThrough();
  });
});

Все 68 Комментарий

У меня тоже есть эта проблема. Насколько я могу судить, поддельный $ httpBackend сохраняет «настоящий» $ httpBackend в качестве делегата, но по какой-то причине этот делегат также является поддельным $ httpBackend, который не ожидает, что запрос приведет к этой ошибке.

У кого-нибудь есть отзывы по этой проблеме? Не могу поверить, насколько сложно написать настоящий интеграционный тест на Angular ... тьфу.

Я обнаружил, что это сработало для меня, когда у меня была эта проблема с моей папкой просмотров. Может быть, для вас это тоже начало.

$httpBackend.whenGET(/views\/.*/).passThrough();

(отсюда https://groups.google.com/forum/?fromgroups=#!topic/angular/ObdxCoCObYU)

И дублируем здесь: # 2512

Я не вижу, где № 2512 на самом деле дубликат. Здесь проблема в том, что не удается создать экземпляр ngMockE2E для httpbackend. Здесь проблема в том, что ngMockE2E делегирует запросы passThrough () к ngMock httpbackend (т.е. не использует настоящий HTTP-сервер в качестве делегата)

У меня тоже есть эта проблема.

В качестве обходного пути я модифицировал angular-mocks.js и закомментировал регистрацию $ HttpBackendProvider в модуле ngMock. Таким образом, $ httpBackend в ngMockE2E будет использовать фактический $ httpBackend в модуле ng при использовании параметра passThrough.
В моих спецификациях, где я использовал ngMock. $ HttpBackend для имитации службы $ http, я добавил ngMockE2E в качестве зависимости моего модуля для запуска моих спецификаций.

У меня тоже есть эта проблема. Эта проблема возникла год назад и все еще существует ...

Я использую версию 1.2.9

Если вы используете ngAnimate и анимируете ngView , добавьте angular-animate.js после angular-mocks.js

это моя установка:

<script src="scripts/vendor/angular-1.2.9/angular.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-route.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-sanitize.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-resource.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-mocks.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-animate.js"></script>
<script src="scripts/app.js"></script>

и образец app.js :

angular.module('app', ['ngRoute', 'ngAnimate', 'ngMockE2E'])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
    'use strict';

    $routeProvider
        .when('/home', {
            controller: 'HomeCtrl',
            templateUrl: 'partials/home.html'
        })
        .otherwise( { redirectTo: '/home' } );

    $locationProvider.html5Mode(false).hashPrefix('!');
}])
.run(['$httpBackend',
    function ($httpBackend) {
        $httpBackend.whenGET(/\.html$/).passThrough();
    }
]);

У меня возникли проблемы при включении angular-animate.js перед angular-mocks.js , я проверил сетевую панель, и файл .html загружался правильно, затем проверил панель элементов и изменил DOM тоже правильно ... изменение порядка загрузки скриптов у меня сработало.

У меня тоже есть эта проблема. Насколько я могу судить, поддельный $ httpBackend сохраняет «настоящий» $ httpBackend в качестве делегата, но по какой-то причине этот делегат также является поддельным $ httpBackend, который не ожидает, что запрос приведет к этой ошибке.

Я также заметил это при попытке отладить эту проблему.

В «настоящем» браузере серверная часть ngMockE2E хранит реальный $ httpBackend как $delegate . Однако, когда я запускаю Jasmine / Karma, по какой-то причине этот делегат не определен, и «настоящий» конструктор $ httpBackend, кажется, никогда не вызывается.

Я еще немного покопаюсь, и это, похоже, артефакт того факта, что инжектор angular-mocks ( window.inject ) автоматически добавляет ng и ngMock как зависимости модуля ко всему, что он вводит.

Загрузка ngMock и ngMockE2E одновременно появляется в кэше providerCache инжектора, что в конечном итоге приводит к ngMockE2E $httpBackend $delegate указывает на ngMock $httpBackend , а не на ng $httpBackend (какова документированная функциональность).

Если мы вручную введем ng $ httpBackend в ngMockE2E, похоже, что делегат настроен правильно ...

Если я внесу следующие изменения в angular-mocks.js, .passThrough() корректно работать в Jasmine и "обычных" браузерах:

@@ -1761,7 +1761,9 @@ angular.module('ngMock', ['ng']).provider({
  * Currently there is only one mock present in this module -
  * the {<strong i="23">@link</strong> ngMockE2E.$httpBackend e2e $httpBackend} mock.
  */
-angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
+angular.module('ngMockE2E', ['ng']).service('$httpBackend', function(){
+   return angular.injector(['ng']).get('$httpBackend');
+}).config(['$provide', function($provide) {
   $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
 }]);

@schmod, зачем вам загружать и ngMock, и ngMockE2E? Они предназначены для использования в разных типах тестов (unit vs. e2e). Смешивание их звучит странно ... Каков ваш случай использования?

Я не пытаюсь загрузить одновременно ngMock и ngMockE2E.

Тем не менее, если я бег жасмина, и используя window.inject функции ngMock предоставлены для меня, хочу ли я его или нет:

window.inject = angular.mock.inject = function() {
  ...  
      modules.unshift('ngMock');
      modules.unshift('ng');

Есть несколько редких случаев, когда я действительно могу захотеть использовать passThrough() в модульном тесте, что потребует от меня включения ngMockE2E .

@schmod хорошо, так каков ваш реальный сценарий проблемы / воспроизведения?

В моем приложении есть служба, которая загружает / кэширует изображения (в IndexedDB как blob ).

При написании модульных тестов для этой службы я хотел бы, чтобы Karma выдавала «реальный» образ для использования моей службой. По практическим соображениям проще просто включить реальное изображение в мой Karma.conf и использовать whenGET().passThrough() вместо .respond() . Я предполагаю, что вы захотите сделать что-то подобное в любое время, когда захотите потреблять большие объемы данных в своих модульных тестах (чтобы не загрязнять спецификации тестов фактическими данными).

Чтобы использовать .passThrough() , мне нужно добавить module('ngMockE2E'); в блок beforeEach моего теста.

При этом я случайно создаю экземпляры ngMock и ngMockE2E , что дает мне неработающую функцию passThrough() на $httpBackend .

Воспроизвести сенарио (внутри Жасмина / Кармы / Хрома):

describe('passThrough', function(){
    var $http, $httpBackend, $rootScope;
    beforeEach(function(){
        module('ngMockE2E');
        inject(function(_$http_, _$httpBackend_, _$rootScope_){
            $http = _$http_;
            $httpBackend = _$httpBackend_;
            $rootScope = _$rootScope_;
        });
    });
    it('should do something', function(){
        $httpBackend.whenGET(/.*image.jpg$/).passThrough();
        $http.get('image.jpg');
        $rootScope.$apply();
    });
});

Это вызывает ошибку:

Error: Unexpected request: GET image.jpg
No more request expected
    at $httpBackend (http://localhost:9050/base/app/scripts/angular-mocks.js:1207:9)
    at $httpBackend (http://localhost:9050/base/app/scripts/angular-mocks.js:1200:11)
    at sendReq (http://localhost:9050/base/app/bower_components/angular/angular.js:7819:9)
    at $http.serverRequest (http://localhost:9050/base/app/bower_components/angular/angular.js:7553:16)
    at wrappedCallback (http://localhost:9050/base/app/bower_components/angular/angular.js:10949:81)
    at wrappedCallback (http://localhost:9050/base/app/bower_components/angular/angular.js:10949:81)
    at http://localhost:9050/base/app/bower_components/angular/angular.js:11035:26
    at Scope.$eval (http://localhost:9050/base/app/bower_components/angular/angular.js:11955:28)
    at Scope.$digest (http://localhost:9050/base/app/bower_components/angular/angular.js:11781:31)
    at Scope.$apply (http://localhost:9050/base/app/bower_components/angular/angular.js:12061:24) 

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

Моки модульных тестов @mstaessen и макеты e2e никогда не предназначались для совместного использования, и я подозреваю, что есть много других проблем, связанных с их совместным использованием.

Делаете ли вы какие-либо фактические утверждения по загруженным изображениям?

$ httpBackend - единственное, что есть в макете E2E, и у нас не было других проблем с этой конфигурацией. Я не вижу веских причин, по которым эти двое не могут складываться друг на друга.

(И да. Мы делаем утверждения для загруженных изображений. Сама служба пытается определить достоверность загружаемых изображений, поэтому важно, чтобы мы могли отправлять ей реальные данные во время тестирования)

Аналогичная проблема возникает здесь:
https://github.com/angular/angular.js/issues/3385

(Обновил мой комментарий выше, чтобы упомянуть фактическую ошибку, которую вызывает мой сценарий воспроизведения.)

Я также изучил это, потому что у меня есть expectGET и whenGET().passThrough для загрузки файла шаблона в beforeEach .

В строке 1165 он вызывает функцию $delegate когда definition.passThrough истинно.

Когда вызывается этот делегат (он снова вызывает $httpBackend() из строки 1112), он, кажется, теряет ожидания, которые я ему дал (вероятно, из-за expectations.shift() в строке 1149, и поэтому он больше не установить как wasExpected .

Это ожидаемое поведение? Я не понимаю, почему он снова вызывает высмеянный $httpBackend при "прохождении".

Согласно описанному выше опыту schmod, я попытался закомментировать строку 1759: $httpBackend: angular.mock.$HttpBackendProvider как часть инициализации angular.module('ngMock', ['ng']).provider() и мои тесты прошли, как ожидалось.

Есть обновления по этому поводу? У меня такая же проблема

Я использую не Karma, а Jasmine test runner. Однако у меня была такая же проблема. Делегат $ - это не настоящая служба $ http, а сам макет.

В любом случае, как schmod и ashclarke, я тоже прокомментировал эту строчку, и она сработала.

На 1.2.18 строка 1742:

$httpBackend: angular.mock.$HttpBackendProvider, 

+1

В некоторых случаях было бы удобно использовать passThrough() внутри модульного теста (например, мне нужно использовать бэкэнд, который я не знаю (с редкой документацией), и я хотел бы написать несколько модульных тестов, это должно ударить по бэкэнду. Когда все заработает, я потом поиздевался над ними)

Хорошо, есть обходной путь, но вы не сможете иметь оба одновременно (модульный тест ожидает некоторый вызов, e2e просто отвечает поддельным или проходящим). Вот как вставить $ httpBackend из e2e в модульный тест

//be careful order is important
describe('inject e2e $httpBackend', function () {

  it('here the injection', function() {
    var $httpBackend;

    //instantiate your modules
    angular.mock.module('ngMockE2E', 'openstack.keystone.v2')

    angular.mock.module(function ($provide) {

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ng'])
      .invoke(function($httpBackend) {
        $provide.value('$delegate', $httpBackend);
      });

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ngMockE2E'])
      .invoke(['$httpBackend', function(_$httpBackend_){
        $httpBackend = _$httpBackend_;
      }]);

      $provide.value('$httpBackend', $httpBackend);
    });

   //and then it works
   $httpBackend.when('POST', '/identity/tokens').passThrough();
  });
});

Что с этим делать? Последний вариант от IxDay - несостоятельное решение. Не может быть, чтобы это было около 100+ юнит-тестов только для того, чтобы в дальнейшем это было бесполезно. Решение ashclarke не работает. Это вызывает не только ошибку при passThrough, но также и ошибки для всех остальных при GET.

Есть ли у нас какие-нибудь новости по этому поводу? У нас есть установка разработчика без бэкэнд, где мы используем e2e $ httpBackend и перехватчик для передачи данных из начального кеша или passThrough, когда это необходимо.

Если мы не можем использовать e2e в модуле, тогда нам придется принудительно использовать mock-объекты с помощью mocker модульного тестирования. И даже тогда у нас возникают реальные проблемы, поскольку у нас, кажется, есть два отдельных $ httpBackends, плавающих в системе, и они каким-то образом указывают друг на друга. Это означает, что мы активировали e2e в модульном тесте, но никогда не отвечаем, что немного бессмысленно.

Есть ли какой-нибудь план сделать разницу между двумя $ httpBackends более понятной? Потому что требуется масса бесплодных поисков в Google, чтобы даже понять, что в игре могут быть два разных модуля.

Подписаться

+1
Также используется Карма с Жасмин и возникает эта проблема.

подписка

Также возникает эта проблема, я использую обходной путь hclarck, который также работает для меня, но не подходит для автоматического тестирования на CI, таком как Travis.
https://github.com/angular/angular.js/issues/1434#issuecomment -44948388.

http://stackoverflow.com/a/26992327/2030950

Я призываю всех, кому нужна эта функция, проверить следующий SO-ответ. Меня устраивает.

Воспроизведено здесь на всякий случай:

angular.module('httpReal', ['ng'])
    .config(['$provide', function($provide) {
        $provide.decorator('$httpBackend', function() {
            return angular.injector(['ng']).get('$httpBackend');
        });
    }])
    .service('httpReal', ['$rootScope', function($rootScope) {
        this.submit = function() {
            $rootScope.$digest();
        };
    }]);

Затем в тесте:

describe('my service', function() {
    var myService, httpReal;

    beforeEach(module('myModule', 'httpReal'));

    beforeEach(inject(function( _myService_, _httpReal_ ) {
        myService = _myService_;
        httpReal = _httpReal_;
    }));

    it('should return valid data', function(done) {
        myService.remoteCall().then(
            function(data) {
                expect(data).toBeDefined();
                done();
            }, function(error) {
                expect(false).toBeTruthy();
                done();
            });

        httpReal.submit();
    });
});

Booyah, модульный тест, который попадает в настоящий бэкэнд на жасмине.

Есть новости по этому поводу? Я уже несколько месяцев использую упомянутый обходной путь @IxDay , но он не идеален.

+1

+1 У меня есть собственное решение для насмешек, использующее sinon.js, и я хочу, чтобы все запросы проходили через

Тоже столкнулся с этой проблемой. У нас есть модуль, который дает нам возможность настраивать поддельный бэкэнд. Это зависит от ngMockE2E . Мне нужно провести модульное тестирование этого модуля.

УДАРЯТЬСЯ!

Да, это абсурд, я бы хотел иметь возможность писать тесты e2e, не полагаясь на настоящий API.

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

Я написал решение в своем блоге: http://www.bradoncode.com/blog/2015/06/16/unit-test-http-ngmock-passthrough/

Здесь та же проблема. Я бы хотел иметь возможность переключаться между поддельными и настоящими http-вызовами.

Одно и тоже!
На самом деле написал сообщение в багтрекере Кармы (https://github.com/karma-runner/karma/issues/1567), но делать это здесь кажется более подходящим.

Можем ли мы получить обновленную информацию по этому поводу?

+1

+1

+1

+1

Закончился использованием решения для статьи в блоге

подписка

+1

+1

+1
Использовал решение @bbraithwaite (спасибо) с исправлением для Angular v1.4.3.

+1

+1

+1

Добавление к припеву +1. Вот сценарий. У нас есть изолированные апартаменты с использованием кармы, жасмина и барда. Все издеваются, ничего страшного. Нам также нужен пакет интеграции, который выполняет полные или частичные сквозные тесты, все еще используя карму и жасмин, но не бард. Он будет делать некоторые http-вызовы, скажем, в наш собственный api, но имитировать другие вызовы, скажем, для amazon s3 put url или api, для выполнения которых требуется слишком много настроек. Наконец, я проведу неразрушающий дымовой тест с использованием транспортира.

Это должно быть возможно, и спасибо всем, кто нашел обходные пути.

+1

+1

+1 Попытка провести модульное тестирование директивы с templateUrl и столкнуться с этой проблемой.

+1 такая же проблема здесь

Ошибка: [$ injector: modulerr ] Не удалось создать экземпляр модуля demoApp из-за:
[$ injector: modulerr ] Не удалось создать экземпляр модуля ngMockE2E из-за:
[$ injector: nomod ] Модуль ngMockE2E недоступен! Вы либо неправильно написали имя модуля, либо забыли его загрузить. При регистрации модуля убедитесь, что вы указали зависимости в качестве второго аргумента.
http://errors.angularjs.org/1.4.8/ $ injector / nomod? p0 = ngMockE2E
minErr / <@ https://code.angularjs.org/1.4.8/angular.js : 68: 12
так далее

На положительной ноте: один комментарий на случай, если это поможет другим. :улыбка:

Я пытался издеваться над серверной частью при тестировании логики внешнего интерфейса в браузере (без модульного теста). У меня возникли некоторые проблемы, и, прочитав этот выпуск, мне показалось, что это подтверждает, что Angular ngMockE2E еще не поддерживает этот сценарий. Однако после двойной проверки для меня с использованием подстановочного знака /.*/ regexp в конце mockrun эта IS работает (я использую Angular 1.3):

angular.module("buyCoApp.services").run(mockRun);
mockRun.$inject = ["$httpBackend", "$http", "_"];
function mockRun($httpBackend, $http) {
    $httpBackend.whenGET("api/test").respond(function(method, url, data) {
     ...
     });
    $httpBackend.whenGET(/.*/).passThrough();
}

Итак, для URL-адреса api/test мой макет, а остальное передается на сервер!

+1

Какие-нибудь решения для этого?

У меня уже несколько месяцев открыт PR. Понятия не имею, что мне нужно сделать, чтобы он был рассмотрен или объединен, но ... он есть.

Я полагаю, что пока нет веских аргументов в пользу необходимости одновременной загрузки и ngMock и ngMockE2E . Совмещение функциональных возможностей модульного и e2e-теста не является хорошей практикой.

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

Только мои 2 цента ...

Проблема в том, что angular-mocks загружает за вас ngMock , независимо от того, хотите вы этого или нет. Я подозреваю, что очень немногие люди _ преднамеренно_ загружают оба, но любое использование angular.mock.inject автоматически добавляет ngMock к внедренному модулю, даже если вы уже загрузили ngMockE2E (таким образом сбивая ngMockE2E $httpBackend )

Нетрудно понять, почему кто-то может захотеть написать интеграционный тест с Jasmine.

В моем случае я хочу протестировать некоторые функции, которые загружают и обрабатывают очень большие файлы (которые иногда являются двоичными). Я бы предпочел не имитировать эти файлы, встраивая их в свои тесты - гораздо проще просто сделать «настоящий» HTTP-запрос для их получения, когда они понадобятся.

Я люблю проводить сервисные тесты, чтобы убедиться, что серверные сервисы по-прежнему работают так, как я ожидал. Слишком часто бэкенд-ребята что-то меняют, не сказав мне, в результате рядом со мной встал тестер :)

@gkalpak : включение функции сквозной передачи непосредственно в ngMock из коробки звучит как лучшее решение. Но я уже был бы счастлив, если бы мне не понадобился сумасшедший взлом, чтобы это работало.

@schmod , если вы уже загрузили ngMockE2E вам не следует запускать модульный тест. В этом случае не следует вызывать angular.mock.inject() . Что я скучаю?

+1

Вот рабочий пример использования ngMockE2E с $ httpBackEnd

require('./test-helper.js');

// our system under test
// which is a wrapper around 
// Yahoo's weather service
//
function sunriseService($http, $log) {
  this.query = function(location) {

    var yql = 
      'select * from weather.forecast    \n\
       where woeid in (                  \n\
         select woeid from geo.places(1) \n\
         where text="' +                 
         location.replace("'", "''") + '")';

     var url = 'https://query.yahooapis.com/v1/public/yql' +
        '?q=' + yql.replace('    ', '') + 
        '&format=json' + 
        '&env=store://datatables.org/alltableswithkeys';

    $log.debug(url);

    return $http.get(url)
        .then(function(result) {
            return result.data.query.results.channel.item.condition;
        });
  }
}

angular.module('app', [])
  .service('sunriseService', function($http, $log) {
      return new sunriseService($http, $log);
   });

//
// test suite
//

// define a new module that takes a dependency on
// our module and the ngMockE2E module
//
//
// !! do not include 'ngMock' in the list
//    as it'll load ngMock and override 
//    $httpBackend
//
angular.module('appDev', ['app', 'ngMockE2E']);

describe('ngMockE2E', function() {

  beforeEach(function () {
    //
    // !! do not use ngMock's module function
    //    as it'll load ngMock and override 
    //    $httpBackend
    //
    angular.module('appDev')
        .run(function ($httpBackend) {
            $httpBackend.whenGET(/.*/).passThrough();
        });
    });

  it('uses $httpBackend passthrough', function (done) {

    //
    // !! do not use ngMock's inject function
    //    as it'll load ngMock and override 
    //    $httpBackend
    //
    $injector = angular.injector(['appDev']);
    $injector.invoke(function(sunriseService, $rootScope) {

      sunriseService.query('brisbane, australia')
        .then(function (forecast) {
            console.log(forecast);
            expect(Object.keys(forecast)).toContain('text');
            done();
        })
        .catch(function (error) {
            done.fail(JSON.stringify(error));
        });
        $rootScope.$digest();
    });
  });
});

Была ли эта страница полезной?
0 / 5 - 0 рейтинги