Введення макету в сервіс AngularJS


114

У мене написана служба AngularJS, і я хотів би перевірити її.

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, однак далі я хочу перевірити службу, що означає глузування з ін'єктованих об'єктів.

Як мені це робити?

Я спробував розмістити свої макетні об’єкти в модулі, наприклад

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, як усі.

Хтось знає, як я можу знущатися над ін'єкційними службами для моїх тестових одиниць?

Дякую

Девід


Ви можете поглянути на цю мою відповідь на інше запитання, я сподіваюся, що це може вам допомогти.
ремігіо

Також зверніть увагу на stackoverflow.com/questions/14238490
джеб

Відповіді:


183

Ви можете вводити макети у свою послугу, використовуючи $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 за посилання на чудове відео .


13
Чудово працює, але будьте уважні до деталей: beforeEach(module('myModule'));дзвінок НАДАЄТЬСЯ до beforeEach(function () { MOCKING })дзвінка, інакше насмішки будуть перезаписані реальними службами!
Нікос Параскевопулос

1
Чи є такий же спосіб знущатися не з обслуговування, а з постійним?
Артем

5
Як і в коментарі Нікоса, будь-які $provideдзвінки потрібно робити, перш ніж використовувати $injectorінше, ви отримаєте помилку:Injector already created, can not register a module!
providencemac

7
Що робити, якщо ваш макет потребує $ q? Тоді ви не можете ввести $ q у макет до виклику модуля (), щоб зареєструвати макет. Будь-які думки?
Джейк

4
Якщо ви використовуєте coffeescript і бачите Error: [ng:areq] Argument 'fn' is not a function, got Object, переконайтеся, що після цього додайте returnрядок $provide.value(...). Невдале повернення $provide.value(...)спричинило для мене цю помилку.
yndolok

4

Те, як я на це дивлюся, не потрібно знущатися над самими службами. Просто знущайтеся над функціями сервісу. Таким чином, ви можете мати кутовий введення ваших реальних послуг, як це робиться в додатку. Потім знущайтеся над функціями сервісу за потребою, використовуючи spyOnфункцію Жасмін .

Тепер, якщо служба сама є функцією, а не об'єктом, яким ви можете користуватися spyOn, є інший спосіб її вирішити. Мені потрібно було це зробити, і я знайшов щось, що працює досить добре для мене. Див. Як ви знущаєтесь над послугою Angular, яка є функцією?


3
Я не думаю, що це відповідає на питання. Що робити, якщо фабрика знущаються сервісу робить щось нетривіальне, як-от потрапити на сервер для даних? Це було б вагомою причиною, щоб хотіти знущатися над цим. Ви хочете уникнути виклику сервера, а натомість створити макетну версію послуги з підробленими даними. Знущання з $ http теж не є хорошим рішенням, тому що ви фактично випробовуєте дві послуги в одному тесті, а не одиничне тестування двох служб ізольовано. Тому я хотів би повторити питання. Як ви передаєте макетну послугу іншій службі в одиничному тесті?
Патрік Арнесен

1
Якщо ви турбуєтесь про те, що служба потрапить на сервер для даних, саме для цього використовується $ httpBackend ( docs.angularjs.org/api/ngMock.$httpBackend ). Я не впевнений, що б ще викликало занепокоєння на заводі служби, що вимагало б глузування з усієї служби.
dnc253

2

Інший варіант, який допоможе полегшити глузливі залежності в 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's dependencies
        });
    });
    ....

Він автоматично управляє всіма згаданими плитами котла, тому вам не доведеться виписувати весь цей макетний код вприскування в кожному тесті. Сподіваюся, що це допомагає.


2

На додаток до відповіді Джона Галамбоса : якщо ви просто хочете знущатися над певними методами послуги, ви можете це зробити так:

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');
  }));

});

1

Якщо ваш контролер написаний для прийому в такій залежності:

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

то ви можете зробити підробку someDependencyв тесті Жасмін так:

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();
    }));
});

9
Питання стосується сервісів, які не отримуються примірниками в тестовому наборі з викликом будь-якої еквівалентної послуги, як контролер $. Іншими словами, людина не викликає $ service () у блоці перед кожним, переходячи в залежності.
Морріс Сінгер

1

Нещодавно я випустив ngImprovedTesting, який повинен полегшити тестування макетів у AngularJS.

Щоб перевірити 'myService' (з модуля «myApp») з його глузливими залежностями fooService та barService, ви можете просто виконати наступне в своєму тесті Жасмін:

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/


1
Чому це було знято? Я не розумію значення в голосуванні без коментарів.
Джейкоб Брюер

0

Я знаю, що це старе питання, але є ще один простіший спосіб, ви можете створити макет і відключити оригінал, який вводиться за допомогою однієї функції, це можна зробити за допомогою 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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.