मैं एंगुलरजेएस जैस्मीन इकाई परीक्षण में वादा वापस करने वाली सेवा का कैसे मजाक उड़ा सकता हूं?


152

मेरे पास myServiceवह उपयोग है myOtherService, जो एक दूरस्थ कॉल करता है, वादे को पूरा करता है:

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

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

myServiceमुझे मॉक करने के लिए एक इकाई परीक्षण करने की आवश्यकता है myOtherService, जैसे कि इसकी makeRemoteCallReturningPromiseविधि एक वादा वापस करती है। यह मेरा इसे करने का तरीका है:

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

जैसा कि आप ऊपर से देख सकते हैं, मेरी मॉक की परिभाषा इस पर निर्भर करती है $q, जिसका उपयोग करके मुझे लोड करना है inject()। इसके अलावा, मॉक को इंजेक्ट किया जाना चाहिए module(), जो पहले आना चाहिए inject()। हालाँकि, इसे बदलने के बाद मॉक के लिए वैल्यू अपडेट नहीं की जाती है।

ऐसा करने का उचित तरीका क्या है?


क्या वास्तव में त्रुटि है myService.makeRemoteCall()? यदि हां, तो समस्या के साथ है myServiceनहीं makeRemoteCall, कुछ भी अपने मज़ाक उड़ाया से कोई लेना देना नहीं myOtherService
dnc253

त्रुटि myService.makeRemoteCall () पर है, क्योंकि myService.myOtherService इस बिंदु पर सिर्फ एक खाली वस्तु है (इसका मान कोणीय द्वारा कभी अद्यतन नहीं किया गया था)
Georgii Oleinikov

आप खाली ऑब्जेक्ट को ioc कंटेनर में जोड़ते हैं, उसके बाद आप संदर्भ myOtherServiceMock को बदलकर एक नई ऑब्जेक्ट को इंगित करते हैं जिस पर आप जासूसी करते हैं। Ioc कंटेनर में व्हाट्सएप ऐसा नहीं है कि संदर्भ बदल गया है।
twDuke

जवाबों:


175

मुझे यकीन नहीं है कि आपने जिस तरह से काम किया है वह काम नहीं करता है, लेकिन मैं आमतौर पर spyOnफ़ंक्शन के साथ करता हूं । कुछ इस तरह:

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

यह भी याद रखें कि फ़ंक्शन को $digestकॉल करने के लिए आपको कॉल thenकरना होगा। $ Q दस्तावेज़ीकरण का परीक्षण अनुभाग देखें ।

------ संपादित ------

आप जो कर रहे हैं, उसे करीब से देखने के बाद, मुझे लगता है कि मुझे आपके कोड में समस्या दिखाई दे रही है। में beforeEach, आप myOtherServiceMockएक पूरी नई ऑब्जेक्ट पर सेट कर रहे हैं । $provideइस संदर्भ कभी नहीं देखेंगे। आपको केवल मौजूदा संदर्भ को अद्यतन करने की आवश्यकता है:

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

1
और आपने परिणाम नहीं दिखाते हुए कल मुझे मार डाला। -CallFake () का सुंदर प्रदर्शन। धन्यवाद।
प्रिया रंजन सिंह

इसके बजाय andCallFakeआप andReturnValue(deferred.promise)(या and.returnValue(deferred.promise)जैस्मीन 2.0+ में) का उपयोग कर सकते हैं । deferredआपको कॉल करने से पहले परिभाषित करने की आवश्यकता है spyOn, बिल्कुल।
जॉर्डन रनिंग

1
$digestजब आप इस दायरे में नहीं होते हैं तो आप इस मामले में कैसे कॉल करेंगे ?
जिम आहो

7
@JimAho आमतौर पर आप बस इंजेक्ट करते हैं $rootScopeऔर $digestउस पर कॉल करते हैं।
dnc253

1
इस मामले में आस्थगित का उपयोग करना अनावश्यक है। आप बस $q.when() codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong
fodma1

69

हम जासूसी के वादे को सीधे जासूस द्वारा लागू करने को भी लिख सकते हैं।

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

जैस्मीन 2 के लिए:

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

(टिप्पणियों से कॉपी किया गया, ccnokes का धन्यवाद)


12
जैस्मीन 2.0, .andReturn () का उपयोग करने वाले लोगों को नोट .and.returnValue द्वारा प्रतिस्थापित किया गया है। तो उपरोक्त उदाहरण यह होगा: spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));मैंने सिर्फ एक आधे घंटे को मार दिया था जो कि बाहर लगा।
19

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
ऐसा मैंने पूर्व में भी किया है। एक जासूस बनाएँ जो एक नकली रिटर्न देता है जो "तत्कालीन" की नकल करता है
डैरेन कॉर्बेट

क्या आप पूरी परीक्षा का उदाहरण दे सकते हैं। मुझे एक ऐसी सेवा की समस्या है जो एक वादा लौटाती है, लेकिन इसके साथ यह एक कॉल भी करता है जो एक वादा लौटाता है!
रॉब पैडॉक

हाय रोब, यह निश्चित नहीं है कि आप एक कॉल क्यों मॉक करना चाहेंगे जो एक मॉक किसी अन्य सेवा के लिए करता है निश्चित रूप से आप उस फ़ंक्शन का परीक्षण करते समय परीक्षण करना चाहते हैं। यदि फ़ंक्शन कॉल आप मॉकिंग कर रहे हैं तो एक सेवा को डेटा मिलता है, फिर उस डेटा को प्रभावित करता है जो आपका नकली वादा एक नकली प्रभावित डेटा सेट लौटाता है, कम से कम मैं यह कैसे करूंगा।
डैरेन कॉर्बेट

मैंने इस रास्ते को शुरू किया और यह सरल परिदृश्यों के लिए बहुत अच्छा काम करता है। मैंने एक मॉक भी बनाया जो चेनिंग को अनुकरण करता है और अधिक जटिल परिदृश्यों में श्रृंखला gist.github.com/marknadig/c3e8f2d3fff9d22da42b को लागू करने के लिए " कीप " / "ब्रेक" हेल्पर्स प्रदान करता है, हालांकि यह नीचे गिरता है। मेरे मामले में, मेरे पास एक ऐसी सेवा थी जो सशर्त रूप से कैश (w / deferred) से आइटम वापस करेगी या एक अनुरोध करेगी। इसलिए, यह अपना वादा बना रहा था।
मार्क नाडिग

यह पोस्ट ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy फर्जी "तब" के उपयोग का वर्णन करती है।
Custodio

8

अपनी सेवा का मजाक उड़ाने के लिए आप सिनॉन जैसे स्टबिंग लाइब्रेरी का उपयोग कर सकते हैं। फिर आप अपने वादे के रूप में $ q.when () वापस कर सकते हैं। यदि आपके स्कोप ऑब्जेक्ट का मान वादा परिणाम से आता है, तो आपको स्कोप कॉल करने की आवश्यकता होगी। $ जड़। $ डाइजेस्ट ()।

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
यह लापता टुकड़ा है, जिसे $rootScope.$digest()हल करने का वादा करने के लिए कॉल किया जा रहा है

2

का उपयोग कर sinon:

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

ज्ञात है, httpPromiseहो सकता है:

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

0

ईमानदारी से .. आप मॉड्यूल के बजाय एक सेवा को नकली करने के लिए इंजेक्शन पर भरोसा करके इस गलत तरीके के बारे में जा रहे हैं। इसके अलावा, पहले से एक में इंजेक्शन कॉल करना एक विरोधी पैटर्न है क्योंकि यह प्रति परीक्षण के आधार पर मज़ाक करना मुश्किल बनाता है।

यहाँ है कि मैं यह कैसे करना होगा ...

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

अब जब आप अपनी सेवा को इंजेक्ट करते हैं तो उपयोग के लिए एक उचित तरीके से नकली विधि होगी।


3
प्रत्येक के पहले का पूरा बिंदु यह है कि यह प्रत्येक परीक्षण से पहले कहा जाता है मुझे नहीं पता कि आप अपने परीक्षण कैसे लिखते हैं, लेकिन व्यक्तिगत रूप से मैं किसी एक फ़ंक्शन के लिए कई परीक्षण लिखता हूं, इसलिए मेरे पास एक सामान्य आधार होगा जो पहले कहा जाएगा प्रत्येक परीक्षण। इसके अलावा आप एंटी-पैटर्न के समझे गए अर्थ को देखना चाह सकते हैं क्योंकि यह सॉफ्टवेयर इंजीनियरिंग के लिए सहयोगी है।
डैरेन कॉर्बेट

0

मैंने पाया कि उपयोगी, छुरा सेवा समारोह sinon.stub () के रूप में। ($ 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 );

नियंत्रक:

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

और परीक्षण

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

this.scope.$digest();

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

-1

कोड स्निपेट:

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

अधिक संक्षिप्त रूप में लिखा जा सकता है:

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
    return $q.resolve('Remote call result');
});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.