AngularJS Jasmine birim testinde söz veren bir hizmeti nasıl alay edebilirim?


152

Ben uzak bir çağrı yapar, sözlerini iade, myServicebu kullanımları var myOtherService:

angular.module('app.myService', ['app.myOtherService'])
  .factory('myService', [
    myOtherService,
    function(myOtherService) {
      function makeRemoteCall() {
        return myOtherService.makeRemoteCallReturningPromise();
      }

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

Bir birim testi yapmak için , yöntemi bir söz döndürür şekilde , myServicealay gerekir . Bunu nasıl yaparım:myOtherServicemakeRemoteCallReturningPromise

describe('Testing remote call returning promise', function() {
  var myService;
  var myOtherServiceMock = {};

  beforeEach(module('app.myService'));

  // I have to inject mock when calling module(),
  // and module() should come before any inject()
  beforeEach(module(function ($provide) {
    $provide.value('myOtherService', myOtherServiceMock);
  }));

  // However, in order to properly construct my mock
  // I need $q, which can give me a promise
  beforeEach(inject(function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock = {
      makeRemoteCallReturningPromise: function() {
        var deferred = $q.defer();

        deferred.resolve('Remote call result');

        return deferred.promise;
      }    
    };
  }

  // Here the value of myOtherServiceMock is not
  // updated, and it is still {}
  it('can do remote call', inject(function() {
    myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
      .then(function() {
        console.log('Success');
      });    
  }));  

Yukarıdan da görebileceğiniz gibi, benim alayımın tanımı, $qkullanmak zorunda olduğum yüke bağlı inject(). Dahası, daha module()önce gelmesi gereken sahte enjekte edilmesi gerekir inject(). Ancak, değiştirdikten sonra sahte değeri güncellenmez.

Bunu yapmanın uygun yolu nedir?


Hata gerçekten açık myService.makeRemoteCall()mı? Eğer öyleyse, sorun sahip myServiceolmamanız makeRemoteCall, alayınızla ilgisi yoktur myOtherService.
dnc253

Hata myService.makeRemoteCall () üzerindedir, çünkü myService.myOtherService bu noktada boş bir nesne (değeri hiçbir zaman açısal olarak güncellenmedi)
Georgii Oleinikov

Boş nesneyi ioc kapsayıcısına eklersiniz, bundan sonra casus olduğunuz yeni bir nesneye işaret etmek için myOtherServiceMock referansını değiştirirsiniz. Iioc kapsayıcısında ne olduğunu yansıtmayacak, referans değiştikçe.
twDuke

Yanıtlar:


175

Yaptığınız yolun neden işe yaramadığından emin değilim, ancak genellikle spyOnişlevle yaparım . Bunun gibi bir şey:

describe('Testing remote call returning promise', function() {
  var myService;

  beforeEach(module('app.myService'));

  beforeEach(inject( function(_myService_, myOtherService, $q){
    myService = _myService_;
    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;
    });
  }

  it('can do remote call', inject(function() {
    myService.makeRemoteCall()
      .then(function() {
        console.log('Success');
      });    
  }));

Ayrıca işlevin $digestçağrılması için bir çağrı yapmanız gerektiğini unutmayın then. $ Q belgelerinin Test bölümüne bakın .

------DÜZENLE------

Yaptığınız işe daha yakından baktıktan sonra, kodunuzda sorun olduğunu düşünüyorum. İçinde, tamamen yeni bir nesneye beforeEachayarlıyorsunuz myOtherServiceMock. $provideBu başvuru görmek asla. Mevcut referansı güncellemeniz yeterlidir:

beforeEach(inject( function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock.makeRemoteCallReturningPromise = function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;   
    };
  }

1
Ve dün sonuçlarda görünmeyerek beni öldürdün. AndCallFake () güzel görüntüsü. Teşekkür ederim.
Priya Ranjan Singh

Bunun yerine andCallFakekullanabilirsiniz andReturnValue(deferred.promise)(veya and.returnValue(deferred.promise)Jasmine 2.0+). Tabii ki deferredaramadan önce tanımlamanız gerekir spyOn.
Ürdün

1
Kapsama $digesterişiminiz olmadığında bu durumda nasıl ararsınız?
Jim Aho

7
@JimAho Genellikle bunu enjekte $rootScopeedip çağırıyorsunuz $digest.
dnc253

1
Bu durumda ertelenmiş kullanmak gereksizdir. Sadece $q.when() codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong
fodma1

69

Ayrıca yasemin geri dönen sözünü doğrudan casus tarafından yazabiliriz.

spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));

Yasemin 2 için:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));

(ccnokes sayesinde yorumlardan kopyalandı)


12
Jasmine 2.0, .andReturn () kullanan kullanıcılara, .and.returnValue ile değiştirildi. Yukarıdaki örnek şöyle olurdu: spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));Bunu anlayarak yarım saat öldürdüm.
ccnokes

13
describe('testing a method() on a service', function () {    

    var mock, service

    function init(){
         return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
                mock = $injector.get('service_that_is_being_mocked');;                    
                service = __serviceUnderTest_;
            });
    }

    beforeEach(module('yourApp'));
    beforeEach(init());

    it('that has a then', function () {
       //arrange                   
        var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
            return {
                then: function (callback) {
                    return callback({'foo' : "bar"});
                }
            };
        });

        //act                
        var result = service.actionUnderTest(); // does cleverness

        //assert 
        expect(spy).toHaveBeenCalled();  
    });
});

1
Geçmişte böyle yaptım. "O zaman" taklit eden sahte döndüren bir casus oluşturun
Darren Corbett

Sahip olduğunuz eksiksiz testin bir örneğini verebilir misiniz? Ben bir söz veren bir hizmet var benzer bir sorun var, ama içinde de bir söz döndüren bir arama yapar!
Rob Paddock

Merhaba Rob, neden bir sahte başka bir hizmet için yaptığı bir arama sahte neden emin değilim ki bu işlevi test ederken test etmek istiyorum. Alay ettiğiniz işlev çağrıları bir servisin veri almasını sağlarsa, alay konusu sözünüzün sahte etkilenen veri kümesini döndüreceğini etkiler, en azından ben böyle yaparım.
Darren Corbett

Bu yola başladım ve basit senaryolar için harika çalışıyor. Hatta zincirleme taklit ve "tutmak" / "sonu" yardımcıları zincirini çağırmak için gist.github.com/marknadig/c3e8f2d3fff9d22da42b sağlayan bir sahte yarattı Daha karmaşık senaryolarda, ancak bu düşüyor. Benim durumumda, bir önbellekten (w / ertelenmiş) öğeleri iade veya bir istekte bulunacak bir hizmet vardı. Yani, kendi vaadini yaratıyordu.
Mark Nadig

Bu yazı ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy sahte "o zaman" kullanımını açıkça tanımlar.
Custodio

8

Hizmetinizi taklit etmek için sinon gibi bir saplama kütüphanesi kullanabilirsiniz. Daha sonra söz olarak $ q.when () döndürebilirsiniz. Kapsam nesnenizin değeri vaat sonucundan geliyorsa, kapsam. $ Root. $ Digest () öğesini çağırmanız gerekir.

var scope, controller, datacontextMock, customer;
  beforeEach(function () {
        module('app');
        inject(function ($rootScope, $controller,common, datacontext) {
            scope = $rootScope.$new();
            var $q = common.$q;
            datacontextMock = sinon.stub(datacontext);
            customer = {id:1};
           datacontextMock.customer.returns($q.when(customer));

            controller = $controller('Index', { $scope: scope });

        })
    });


    it('customer id to be 1.', function () {


            scope.$root.$digest();
            expect(controller.customer.id).toBe(1);


    });

2
bu eksik parça, $rootScope.$digest()çözülecek sözü almaya çağırıyor

2

kullanarak sinon:

const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
                     .returns(httpPromise(200));

Bilinen, httpPromiseşunlar olabilir:

const httpPromise = (code) => new Promise((resolve, reject) =>
  (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);

0

Dürüst olmak gerekirse .. modül yerine bir hizmet alay etmek için enjekte güvenerek bu yanlış bir şekilde gidiyorsunuz. Ayrıca, bir beforeEach içinde enjekte edilmesi, her test esasında alay etmeyi zorlaştırdığı için bir anti-kalıptır.

İşte böyle yapardım ...

module(function ($provide) {
  // By using a decorator we can access $q and stub our method with a promise.
  $provide.decorator('myOtherService', function ($delegate, $q) {

    $delegate.makeRemoteCallReturningPromise = function () {
      var dfd = $q.defer();
      dfd.resolve('some value');
      return dfd.promise;
    };
  });
});

Şimdi servisinizi enjekte ettiğinizde, kullanım için uygun şekilde alay konusu olacak.


3
Her birinden önce bütün mesele, her testten önce çağrılmasıdır. Testlerinizi nasıl yazdığınızı bilmiyorum, ancak şahsen tek bir işlev için birden fazla test yazıyorum, bu nedenle daha önce çağrılacak ortak bir temel kuracağım her test. Ayrıca, anti-modelin yazılım mühendisliğiyle ilişkili olduğu için anlaşılan anlamını aramak isteyebilirsiniz.
Darren Corbett

0

Sinon.stub () gibi yararlı, bıçaklama hizmeti işlevini döndürdüm ($ q.when ({})):

this.myService = {
   myFunction: sinon.stub().returns( $q.when( {} ) )
};

this.scope = $rootScope.$new();
this.angularStubs = {
    myService: this.myService,
    $scope: this.scope
};
this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );

kontrol:

this.someMethod = function(someObj) {
   myService.myFunction( someObj ).then( function() {
        someObj.loaded = 'bla-bla';
   }, function() {
        // failure
   } );   
};

ve test et

const obj = {
    field: 'value'
};
this.ctrl.someMethod( obj );

this.scope.$digest();

expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );

-1

Kod pasajı:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
    var deferred = $q.defer();
    deferred.resolve('Remote call result');
    return deferred.promise;
});

Daha özlü bir şekilde yazılabilir:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
    return $q.resolve('Remote call result');
});
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.