Внедрение макета в службу AngularJS

У меня есть служба AngularJS, и я хотел бы unit test it.

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) {

    this.something = function() {
        // Do something with the injected services
    };

    return this;
});

В моем файле app.js зарегистрировано:

angular
.module('myApp', ['fooServiceProvider','barServiceProvider','myServiceProvider']
)

Я могу проверить, что DI работает как таковой:

describe("Using the DI framework", function() {
    beforeEach(module('fooServiceProvider'));
    beforeEach(module('barServiceProvider'));
    beforeEach(module('myServiceProvder'));

    var service;

    beforeEach(inject(function(fooService, barService, myService) {
        service=myService;
    }));

    it("can be instantiated", function() {
        expect(service).not.toBeNull();
    });
});

Это доказало, что сервис может быть создан каркасом DI, но затем я хочу unit test службу, что означает издевательство над вложенными объектами.

Как мне это сделать?

Я пробовал помещать свои модулированные объекты в модуль, например.

beforeEach(module(mockNavigationService));

и переписывая определение службы как:

function MyService(http, fooService, barService) {
    this.somthing = function() {
        // Do something with the injected services
    };
});

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) { return new MyService($http, fooService, barService); })

Но последнее, похоже, прекращает создание службы DI как все.

Кто-нибудь знает, как я могу издеваться над внедренными службами для своих модульных тестов?

Спасибо

Дэвид

Ответ 1

Вы можете вводить mocks в свою службу, используя $provide.

Если у вас есть следующая служба с зависимостью, которая имеет метод getSomething:

angular.module('myModule', [])
  .factory('myService', function (myDependency) {
        return {
            useDependency: function () {
                return myDependency.getSomething();
            }
        };
  });

Вы можете ввести макетную версию myDependency следующим образом:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(function () {

      mockDependency = {
          getSomething: function () {
              return 'mockReturnValue';
          }
      };

      module(function ($provide) {
          $provide.value('myDependency', mockDependency);
      });

  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});

Обратите внимание, что из-за вызова $provide.value вам не нужно явно вводить myDependency в любом месте. Это происходит под капотом во время инъекции myService. При настройке mockDependency здесь он мог бы также легко быть шпионом.

Спасибо loyalBrown за ссылку отличное видео.

Ответ 2

Как я смотрю на это, нет необходимости издеваться над самими услугами. Просто издевайтесь над функциями службы. Таким образом, вы можете использовать angular свои реальные услуги, как и во всем приложении. Затем, при необходимости используя функции Jasmine spyOn, высмеивайте функции в сервисе.

Теперь, если сама служба - это функция, а не объект, с которым вы можете использовать spyOn, есть и другой способ. Мне нужно было это сделать, и я нашел что-то, что хорошо работает для меня. См. Как вы издеваетесь над сервисом angular, который является функцией?

Ответ 3

Еще одна возможность помочь облегчить насмешливые зависимости в Angular и Jasmine - использовать QuickMock. Его можно найти на GitHub и позволяет создавать простые макеты в многоразовом режиме. Вы можете клонировать его из GitHub по ссылке ниже. README довольно понятно, но, надеюсь, это может помочь другим в будущем.

https://github.com/tennisgent/QuickMock

describe('NotificationService', function () {
    var notificationService;

    beforeEach(function(){
        notificationService = QuickMock({
            providerName: 'NotificationService', // the provider we wish to test
            moduleName: 'QuickMockDemo',         // the module that contains our provider
            mockModules: ['QuickMockDemoMocks']  // module(s) that contains mocks for our provider dependencies
        });
    });
    ....

Он автоматически управляет всем описанным выше шаблоном, поэтому вам не нужно выписывать весь этот код ложных инъекций в каждом тесте. Надеюсь, что это поможет.

Ответ 4

В дополнение к ответ Джона Галамбоса: если вы просто хотите издеваться над определенными методами службы, вы можете сделать это следующим образом:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(module(function ($provide, myDependencyProvider) {
      // Get an instance of the real service, then modify specific functions
      mockDependency = myDependencyProvider.$get();
      mockDependency.getSomething = function() { return 'mockReturnValue'; };
      $provide.value('myDependency', mockDependency);
  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});

Ответ 5

Если ваш контроллер написан так, чтобы принимать зависимость, например:

app.controller("SomeController", ["$scope", "someDependency", function ($scope, someDependency) {
    someDependency.someFunction();
}]);

то вы можете сделать подделку someDependency в тесте Jasmine следующим образом:

describe("Some Controller", function () {

    beforeEach(module("app"));


    it("should call someMethod on someDependency", inject(function ($rootScope, $controller) {
        // make a fake SomeDependency object
        var someDependency = {
            someFunction: function () { }
        };

        spyOn(someDependency, "someFunction");

        // this instantiates SomeController, using the passed in object to resolve dependencies
        controller("SomeController", { $scope: scope, someDependency: someDependency });

        expect(someDependency.someFunction).toHaveBeenCalled();
    }));
});

Ответ 6

Недавно я выпустил ngImprovedTesting, который должен сделать макетное тестирование в режиме AngularJS проще.

Чтобы проверить "myService" (из модуля "myApp" ) с помощью зависимостей fooService и barService, вы можете сделать следующее в своем тесте Jasmine:

beforeEach(ModuleBuilder
    .forModule('myApp')
    .serviceWithMocksFor('myService', 'fooService', 'barService')
    .build());

Для получения дополнительной информации о ngImprovedTesting ознакомьтесь со своим вступительным сообщением в блоге: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/

Ответ 7

Я знаю, что это старый вопрос, но есть еще один более простой способ: вы можете создать mock и отключить исходный код, введенный в одну функцию, это можно сделать, используя spyOn для всех методов. см. код ниже.

var mockInjectedProvider;

    beforeEach(function () {
        module('myModule');
    });

    beforeEach(inject(function (_injected_) { 
      mockInjectedProvider  = mock(_injected_);
    });

    beforeEach(inject(function (_base_) {
        baseProvider = _base_;
    }));

    it("injectedProvider should be mocked", function () {
    mockInjectedProvider.myFunc.andReturn('testvalue');    
    var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
        expect(resultFromMockedProvider).toEqual('testvalue');
    }); 

    //mock all service methods
    function mock(angularServiceToMock) {

     for (var i = 0; i < Object.getOwnPropertyNames(angularServiceToMock).length; i++) {
      spyOn(angularServiceToMock,Object.getOwnPropertyNames(angularServiceToMock)[i]);
     }
                return angularServiceToMock;
    }