AngularJS hizmetine bir model enjekte etme


114

Yazılı bir AngularJS hizmetim var ve birim testi yapmak istiyorum.

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

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

    return this;
});

App.js dosyam şu kayıtlı:

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

DI'nin şu şekilde çalıştığını test edebilirim:

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

Bu, hizmetin DI çerçevesi tarafından oluşturulabileceğini kanıtladı, ancak daha sonra hizmeti birim test etmek istiyorum, bu da enjekte edilen nesnelerle alay etmek anlamına geliyor.

Bunu nasıl yapacağım?

Taklit nesnelerimi modüle koymayı denedim, örneğin

beforeEach(module(mockNavigationService));

ve hizmet tanımını şu şekilde yeniden yazarak:

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

Ancak ikincisi, DI tarafından yaratılan hizmeti durduruyor gibi görünüyor.

Birim testlerim için enjekte edilen hizmetlerle nasıl dalga geçebileceğimi bilen var mı?

Teşekkürler

David


Bu cevabıma başka bir soruya bakabilirsiniz, umarım size yardımcı olur.
remigio

Yanıtlar:


183

Kullanarak hizmetinize taklitler ekleyebilirsiniz $provide.

GetSomething adlı bir yönteme sahip bir bağımlılığa sahip aşağıdaki hizmete sahipseniz:

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

MyDependency'nin sahte bir sürümünü aşağıdaki gibi enjekte edebilirsiniz:

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.valueSize yapılan çağrı nedeniyle, aslında myDependency'yi herhangi bir yere açıkça enjekte etmeniz gerekmediğini unutmayın. MyService enjeksiyonu sırasında kaputun altında olur. MockDependency'yi burada kurarken, bir casus da olabilir.

Bu harika videonun bağlantısı için loyalBrown'a teşekkürler .


13
Büyük çalışır, ancak bir ayrıntıyı dikkat: beforeEach(module('myModule'));Çağrı HAS önce gelmek beforeEach(function () { MOCKING })çağrı veya başka mocks gerçek servisleri tarafından üzerine yazılır alacak!
Nikos Paraskevopoulos

1
Hizmetle değil, aynı şekilde sürekli olarak alay etmenin bir yolu var mı?
Artem

5
Nikos'un yorumuna benzer şekilde, herhangi bir $providearama kullanılmadan önce yapılmalıdır $injector, aksi takdirde bir hata alırsınız:Injector already created, can not register a module!
Providencemac

7
Ya modelinizin q $ 'a ihtiyacı varsa? O zaman mock'u kaydetmek için module () 'yi çağırmadan önce mock'a $ q enjekte edemezsiniz. Düşüncesi olan var mı?
Jake,

4
Kahve kağıdı kullanıyorsanız ve görüyorsanız Error: [ng:areq] Argument 'fn' is not a function, got Object, returnsonraki satıra bir tane koyduğunuzdan emin olun $provide.value(...). Örtük olarak geri dönmek $provide.value(...)bu hataya neden oldu.
yndolok

4

Benim baktığım şekilde, hizmetlerle alay etmeye gerek yok. Sadece hizmetteki işlevlerle alay edin. Bu şekilde, uygulama genelinde olduğu gibi gerçek hizmetlerinizi açısal olarak yerleştirebilirsiniz. Ardından, Jasmine'in spyOnişlevini kullanarak hizmetteki işlevlerle gerektiği gibi alay edin .

Şimdi, hizmetin kendisi bir işlevse ve kullanabileceğiniz bir nesne değilse spyOn, bununla ilgili başka bir yol vardır. Bunu yapmam gerekiyordu ve benim için oldukça iyi çalışan bir şey buldum. Bkz Eğer bir işlevdir Açısal hizmet alay nasıl?


3
Bunun soruyu yanıtladığını sanmıyorum. Ya alay edilen hizmetin fabrikası, veri için sunucuya vurmak gibi önemsiz olmayan bir şey yaparsa? Bu onunla dalga geçmek için iyi bir neden olur. Sunucu aramasından kaçınmak ve bunun yerine sahte verilerle hizmetin sahte bir sürümünü oluşturmak istiyorsunuz. $ Http ile dalga geçmek de iyi bir çözüm değildir, çünkü o zaman iki hizmeti tek başına birim testi yapmak yerine aslında tek bir testte iki hizmeti test ediyorsunuz. Bu yüzden soruyu tekrar tekrarlamak istiyorum. Bir birim testinde sahte bir hizmeti başka bir hizmete nasıl geçirirsiniz?
Patrick Arnesen

1
Hizmetin veri için sunucuya çarpmasından endişe ediyorsanız, $ httpBackend bunun için ( docs.angularjs.org/api/ngMock.$httpBackend ). Hizmetin fabrikasında tüm hizmetle alay etmeyi gerektirecek başka ne tür bir endişe olurdu emin değilim.
dnc253

2

Angular ve Jasmine'de alay bağımlılıklarını kolaylaştırmaya yardımcı olacak başka bir seçenek de QuickMock'u kullanmaktır. GitHub'da bulunabilir ve yeniden kullanılabilir bir şekilde basit örnekler oluşturmanıza olanak tanır. Aşağıdaki bağlantı aracılığıyla GitHub'dan klonlayabilirsiniz. README oldukça açıklayıcıdır, ancak umarım gelecekte başkalarına yardımcı olabilir.

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

Yukarıda bahsedilen tüm standart kodlarını otomatik olarak yönetir, böylece her testte tüm bu sahte enjeksiyon kodunu yazmanız gerekmez. Umarım yardımcı olur.


2

John Galambos'un cevabına ek olarak : Bir hizmetin belirli yöntemleriyle alay etmek istiyorsanız, bunu şu şekilde yapabilirsiniz:

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

Denetleyiciniz böyle bir bağımlılık alacak şekilde yazıldıysa:

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

o zaman someDependencyböyle bir Jasmine testinde sahte yapabilirsiniz :

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
Soru, test paketinde $ controller olarak eşdeğer herhangi bir hizmete yapılan çağrı ile somutlaştırılmayan hizmetler hakkındadır. Diğer bir deyişle, bağımlılıkları geçerek her bloğun öncesinde $ service () çağırılmaz.
Morris Singer

1

Yakın zamanda AngularJS'de sahte testi daha kolay hale getirmesi gereken ngImprovedTesting'i yayınladım.

FooService ve barService bağımlılıkları alaylı olarak 'myService'i ("myApp" modülünden) test etmek için Jasmine testinizde aşağıdakileri yapabilirsiniz:

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

NgImprovedTesting hakkında daha fazla bilgi için giriş blog gönderisine bakın: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
Bu neden reddedildi? Yorum yapmadan aşağı oylamanın değerini anlamıyorum.
Jacob Brewer

0

Bunun eski bir soru olduğunu biliyorum ama daha kolay bir yol daha var, alay oluşturabilir ve bir işlevde enjekte edilen orijinali devre dışı bırakabilirsiniz, tüm yöntemlerde spyOn kullanılarak yapılabilir. aşağıdaki koda bakın.

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;
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.