Внедрение зависимых сервисов при модульном тестировании сервисов AngularJS

Я тестирую службу A, но служба A зависит от службы B (т. е. служба B внедряется в службу A).

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

Вот пример теста:

describe("Sample Test Suite", function() {

  beforeEach(function() {

    module('moduleThatContainsServiceA');

    inject([
      'serviceA', function(service) {
        this.service = service;
      }
    ]);

  });

  it('can create an instance of the service', function() {
    expect(this.service).toBeDefined();
  });
});

Ошибка, которую я получаю:

Ошибка: Неизвестный провайдер: serviceBProvider

Как я мог сделать что-то подобное?


person Roy Truelove    schedule 09.01.2013    source источник
comment
FWIW: я задал версию QUnit этого вопроса здесь, на CodeReview.SE .   -  person Jeroen    schedule 06.08.2015


Ответы (5)


На самом деле в AngularJS Dependency Injection используется правило «последних побед». Таким образом, вы можете определить свой сервис в своем тесте сразу после включения вашего модуля и зависимостей, а затем, когда тестируемый вами сервис A будет запрашивать сервис B с помощью DI, AngularJS предоставит фиктивную версию сервиса B.

Это часто делается путем определения нового модуля, такого как MyAppMocks, размещения там фиктивных сервисов/значений, а затем простого добавления этого модуля в качестве зависимости.

Вид (схематично):

beforeEach(function() {
  angular.module('MyAppMocks',[]).service('B', ...));
  angular.module('Test',['MyApp','MyAppMocks']);
  ...
person Valentyn Shybanov    schedule 09.01.2013
comment
Вы только что спасли мне жизнь! :D Я убивал себя, пытаясь внедрить фиктивный сервис в другой сервис, который от него зависел, но только в тестах использовалась фиктивная версия, а не внедренный сервис. Теперь у меня есть отдельный фиктивный модуль, который я загружаю после модуля приложения, который перезаписывает нужные службы. Работает как шарм! - person Thomas Fankhauser; 06.03.2013
comment
Томас, не могли бы вы поделиться некоторыми подробностями о вашем решении? У меня 2 модуля, каждый из них содержит сервис, причем сервис_1 из первого модуля инжектируется в сервис_2 во втором модуле. Я создаю фиктивный модуль с сервисом_1, который должен перезаписать исходный сервис_1. И он перезаписывается, но только в тестах, поэтому, когда я вызываю сервис_2, он все еще использует внутри оригинальный сервис_1. - person Selvam Palanimalai; 30.04.2013
comment
Как я обрабатываю добавление макетов в сервисы - person SavoryBytes; 02.10.2013
comment
Хотя я уверен, что этот ответ правильный, я немного запутался с .... Я думаю об этом в контексте, когда хочу протестировать контроллер в модуле, который зависит от MyApp. Что происходит в...? Проверяю ли я сейчас функциональность модуля Test? - person clearf; 24.10.2016
comment
@clearf in ... вы определяете макет службы «B», который обеспечивает необходимую функциональность для тестирования контроллера. То есть, по сути, вы реализуете только часть сервиса B, создавая шпионов (например) и убеждаетесь в конце теста, что все необходимые методы сервиса B были вызваны. - person Valentyn Shybanov; 25.10.2016
comment
Также проголосовали за это как за спасение жизни! Я продолжал задаваться вопросом, почему настоящий $routeProvider был введен вместо моего макета $routeProvider. Короткое, но очень полезное объяснение выше указало на правильный порядок для модулей перед каждым: ngRoute ТОГДА фиктивный ТОГДА тестируемый модуль, который зависит от ngRoute. Без первоначальной (и, казалось бы, бесполезной) ссылки на стандартный модуль он будет загружен вместе с тестируемым модулем, перекрывая макет. - person avat; 19.07.2019

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

describe 'serviceA', ->
   mockServiceB = {}

   beforeEach module 'myApp' # (or just 'myApp.services')

   beforeEach ->
      angular.mock.module ($provide) ->
         $provide.value 'serviceB', mockServiceB
         null

   serviceA = null
   beforeEach inject ($injector) ->
      serviceA = $injector.get 'serviceA'

   it 'should work', ->
      expect( true ).toBe( true )
      #serviceA.doStuff()

Без явного возврата null после $provide.value я продолжал получать Error: Argument 'fn' is not a function, got Object. Я нашел ответ в этой ветке групп Google.

person jab    schedule 13.06.2013
comment
Попробуйте использовать пустой return вместо null. Таким образом, ваш сгенерированный javascript не будет иметь дополнительную строку return null в конце функции. Вместо этого ваша функция beforeEach просто ничего не вернет. - person demisx; 22.05.2014
comment
Я пытаюсь использовать аналогичный код в данный момент, но я получаю SyntaxError: Unexpecte string 'serviceA', если буду использовать ваш код. Любая идея о том, как решить эту проблему? -- Также с помощью Coffeescript - person Mathieu Brouwers; 29.01.2016
comment
@MathieuBrouwers, вам, вероятно, следует открыть новый вопрос. Я не уверен, что именно вы имеете в виду, и мне нужно увидеть ваш код, чтобы ответить. - person jab; 02.02.2016
comment
Я обнаружил проблему к настоящему времени, это было, когда я пытался на самом деле протестировать CoffeeScript вместо скомпилированного кода javascript. Я думаю, что мой комментарий бесполезен и может быть удален сейчас. - person Mathieu Brouwers; 02.02.2016

Решение Валентина сработало для меня, но есть и другая альтернатива.

beforeEach(function () {

    angular.mock.module("moduleThatContainsServiceA", function ($provide) {
                $provide.value('B', ...);
            });
});

Затем, когда служба A AngularJS запрашивает службу B путем внедрения зависимостей, вместо службы B будет предоставлен ваш макет службы B из moduleThatContainsServiceA.

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

person Richard Keller    schedule 20.05.2013
comment
Отлично. В моем случае «...» было заменено на «{}». Бам, полностью удалил зависимость. - person 2mia; 23.03.2015
comment
Проблема в том, что вы все еще издеваетесь над своим сервисом внутри теста. Скажите, что вам нужно помокнуть это в нескольких местах, и что-то изменится. Тогда вам придется менять каждый файл, а не менять в одном месте. Ради ремонтопригодности ответ Валентина должен быть принятым (каким он и является). - person perry; 20.05.2016

Я считаю, что самый простой способ — просто внедрить службу B и издеваться над ней. например Обслуживание автомобиля зависит от обслуживания двигателя. Теперь нам нужно имитировать Engine при тестировании Car:

describe('Testing a car', function() {
      var testEngine;

  beforeEach(module('plunker'));
  beforeEach(inject(function(engine){
    testEngine = engine;
  }));

  it('should drive slow with a slow engine', inject(function(car) {
    spyOn(testEngine, 'speed').andReturn('slow');
    expect(car.drive()).toEqual('Driving: slow');
  }));
});

Ссылка: https://github.com/angular/angular.js/issues/1635

person gm2008    schedule 27.07.2014

Это то, что сработало для меня. Ключом является определение реального модуля, который будет издеваться. Вызов angular.mock.module делает реальный модуль фиктивным и позволяет соединять вещи.

    beforeEach( ->
        @weather_service_url = '/weather_service_url'
        @weather_provider_url = '/weather_provider_url'
        @weather_provider_image = "test.jpeg"
        @http_ret = 'http_works'
        module = angular.module('mockModule',[])
        module.value('weather_service_url', @weather_service_url)
        module.value('weather_provider_url', @weather_provider_url)
        module.value('weather_provider_image', @weather_provider_image)
        module.service('weather_bug_service', services.WeatherBugService)

        angular.mock.module('mockModule')

        inject( ($httpBackend,weather_bug_service) =>
            @$httpBackend = $httpBackend
            @$httpBackend.when('GET', @weather_service_url).respond(@http_ret)
            @subject = weather_bug_service
        )
    )
person Nick    schedule 12.08.2013