Wstrzyknięcie makiety do usługi AngularJS


114

Mam napisaną usługę AngularJS i chciałbym ją przetestować.

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

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

    return this;
});

Mój plik app.js zawiera te zarejestrowane:

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

Mogę sprawdzić, czy DI działa jako takie:

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

Udowodniło to, że usługa może być utworzona przez framework DI, jednak następnie chcę przeprowadzić testy jednostkowe usługi, co oznacza mockowanie wstrzykniętych obiektów.

Jak mam to zrobić?

Próbowałem umieścić w module moje pozorowane obiekty np

beforeEach(module(mockNavigationService));

i przepisanie definicji usługi jako:

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

Ale to drugie wydaje się powstrzymywać tworzenie usługi przez DI jak wszystko.

Czy ktoś wie, jak mogę wyszydzać wstrzyknięte usługi dla moich testów jednostkowych?

Dzięki

David


Można spojrzeć na to odpowiedź kopalni na inne pytanie, mam nadzieję, że to może być pomocne dla Ciebie.
remigio

Odpowiedzi:


183

Możesz wstrzyknąć makiety do swojej usługi za pomocą $provide.

Jeśli masz następującą usługę z zależnością, która ma metodę o nazwie getSomething:

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

Możesz wstrzyknąć pozorowaną wersję myDependency w następujący sposób:

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

});

Zauważ, że ze względu na wezwanie do $provide.value ciebie nie musisz nigdzie jawnie wstrzykiwać myDependency. Dzieje się to pod maską podczas wstrzykiwania myService. Konfigurując tutaj mockDependency, równie łatwo może być szpiegiem.

Dzięki loyalBrown za link do tego świetnego filmu .


13
Działa świetnie, ale uważaj na szczegóły: beforeEach(module('myModule'));połączenie MUSI nastąpić przed beforeEach(function () { MOCKING })połączeniem, w przeciwnym razie makiety zostaną nadpisane przez prawdziwe usługi!
Nikos Paraskevopoulos

1
Czy jest sposób, aby kpić nie ze służby, ale z ustawicznego w ten sam sposób?
Artem

5
Podobnie jak w komentarzu Nikosa, $provideprzed użyciem należy wykonać wszystkie połączenia $injector, w przeciwnym razie pojawi się błąd:Injector already created, can not register a module!
Providencemac

7
A co, jeśli twój model potrzebuje $ q? Wtedy nie możesz wstrzyknąć $ q do makiety przed wywołaniem module () w celu zarejestrowania makiety. jakieś pomysły?
Jake

4
Jeśli używasz coffeescript i widzisz Error: [ng:areq] Argument 'fn' is not a function, got Object, pamiętaj, aby wstawić znak returnpo $provide.value(...). Niejawny powrót $provide.value(...)spowodował ten błąd.
yndolok

4

Patrząc na to, nie ma potrzeby kpić z samych usług. Po prostu mock funkcje w usłudze. W ten sposób możesz mieć Angular wstrzykiwać swoje prawdziwe usługi, tak jak ma to miejsce w całej aplikacji. Następnie mock funkcje w usłudze w razie potrzeby, używając spyOnfunkcji Jasmine .

Teraz, jeśli sama usługa jest funkcją, a nie obiektem, z którym można korzystać, można to zrobić spyOnw inny sposób. Musiałem to zrobić i znalazłem coś, co działa całkiem dobrze. Zobacz Jak kpisz z usługi Angular, która jest funkcją?


3
Nie sądzę, żeby to odpowiadało na pytanie. Co się stanie, jeśli fabryka usługi, z której kpisz, zrobi coś nietrywialnego, na przykład uderzy w serwer w poszukiwaniu danych? To byłby dobry powód, żeby chcieć z tego kpić. Chcesz uniknąć wywołania serwera i zamiast tego utworzyć fałszywą wersję usługi z fałszywymi danymi. Mockowanie $ http również nie jest dobrym rozwiązaniem, ponieważ w rzeczywistości testujesz dwie usługi w jednym teście, zamiast testowania jednostkowego dwóch usług osobno. Dlatego chciałbym powtórzyć pytanie. Jak przekazać usługę pozorowaną do innej usługi w teście jednostkowym?
Patrick Arnesen,

1
Jeśli martwisz się, że usługa trafi do serwera w poszukiwaniu danych, do tego służy $ httpBackend ( docs.angularjs.org/api/ngMock.$httpBackend ). Nie jestem pewien, co innego byłoby problemem w fabryce usługi, co wymagałoby kpiny z całej usługi.
dnc253

2

Inną opcją ułatwiającą mockowanie zależności w Angular i Jasmine jest użycie QuickMock. Można go znaleźć na GitHub i umożliwia tworzenie prostych makiet w sposób wielokrotnego użytku. Możesz sklonować go z GitHub za pomocą poniższego linku. README jest dość zrozumiały, ale miejmy nadzieję, że może pomóc innym w przyszłości.

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

Automatycznie zarządza wszystkimi wymienionymi powyżej wzorcami, więc nie musisz pisać całego tego próbnego kodu wtrysku w każdym teście. Mam nadzieję, że to pomoże.


2

Oprócz odpowiedzi Johna Galambosa : jeśli chcesz po prostu wyszydzić określone metody usługi, możesz to zrobić w ten sposób:

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

Jeśli twój kontroler jest napisany tak, aby przyjmował taką zależność:

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

wtedy możesz zrobić fałszywkę someDependencyw teście Jasmine w następujący sposób:

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
Pytanie dotyczy usług, które nie są uruchamiane w zestawie testów z wywołaniem dowolnej równoważnej usługi jako $ controller. Innymi słowy, nie wywołuje się $ service () w bloku beforeEach, przekazując zależności.
Morris Singer

1

Niedawno wydałem ngImprovedTesting, które powinno znacznie ułatwić testowanie próbne w AngularJS.

Aby przetestować 'myService' (z modułu "myApp") z jego zależnościami fooService i barService, możesz w prosty sposób wykonać następujące czynności w swoim teście Jasmine:

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

Aby uzyskać więcej informacji na temat ngImprovedTesting, zapoznaj się z wprowadzającym wpisem na blogu: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
Dlaczego ten głos został odrzucony? Nie rozumiem wartości głosowania w dół bez komentarza.
Jacob Brewer

0

Wiem, że to stare pytanie, ale jest inny łatwiejszy sposób, możesz utworzyć próbę i wyłączyć oryginalny wstrzyknięty w jednej funkcji, można to zrobić za pomocą spyOn na wszystkich metodach. zobacz kod poniżej.

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;
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.