AngularJS Jasmine単体テストでpromiseを返すサービスをモックするにはどうすればよいですか?


152

私はmyServiceそれを使用myOtherServiceして、リモート呼び出しを行い、promiseを返します。

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

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

ユニットテストを作成するには、そのメソッドがpromiseを返すようmyServiceにモックする必要があります。これが私のやり方です: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');
      });    
  }));  

上記からわかるように、私のモックの定義は、$qを使用してロードする必要があるに依存していますinject()。さらに、モックの注入はで行われるmodule()はずinject()です。ただし、モックの値は、変更すると更新されません。

これを行う適切な方法は何ですか?


エラーは本当にオンmyService.makeRemoteCall()ですか?その場合、問題があるとmyService持っていないmakeRemoteCall、あなたの嘲笑を行うには、ないものをmyOtherService
dnc253、2014年

エラーはmyService.makeRemoteCall()にあります。これは、myService.myOtherServiceがこの時点で空のオブジェクトになっているためです(その値はAngularによって更新されませんでした)
Georgii Oleinikov '16

空のオブジェクトをiocコンテナーに追加した後、参照myOtherServiceMockを変更して、スパイする新しいオブジェクトを指すようにします。参照が変更されたため、iocコンテナーの内容はそれを反映しません。
twDuke、2015

回答:


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

また$digestthen関数を呼び出すには呼び出しを行う必要があることも覚えておいてください。$ 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
そして、あなたは昨日結果に現れないことで私を殺した。andCallFake()の美しい表示。ありがとうございました。
Priya Ranjan Singh 2014

代わりにandCallFake使用できますandReturnValue(deferred.promise)(またはand.returnValue(deferred.promise)Jasmine 2.0以降)。もちろんdeferredを呼び出す前に定義する必要がありますspyOn
ジョーダン

1
$digestこの場合、スコープにアクセスできないときにどのように呼び出しますか?
ジム・アホ

7
@JimAho通常、それを注入$rootScopeして呼び出すだけ$digestです。
dnc253 2015年

1
この場合、遅延を使用する必要はありません。$q.when() codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrongを
fodma1

69

また、jasmineのプロミスを返すスパイの実装を直接スパイで記述することもできます。

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

ジャスミン2の場合:

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

(ccnokesのおかげでコメントからコピー)


12
Jasmine 2.0を使用している人への注意、.andReturn()は.and.returnValueに置き換えられました。したがって、上記の例は次のようになりますspyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
2015

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
これは私が過去にそれをした方法です。"then"を模倣した偽物を返すスパイを作成する
Darren Corbett 2015

あなたが持っている完全なテストの例を提供できますか?promiseを返すサービスがあるという同様の問題がありますが、その中でpromiseを返す呼び出しも行います!
Rob Paddock 2015

こんにちはロブ、モックが別のサービスに対して行う呼び出しをモックしたい理由がわからないので、その関数をテストするときにそれをテストしたいと思うはずです。モック呼び出しの関数呼び出しがサービスがデータを取得する場合、モックされたプロミスが偽の影響を受けるデータセットを返すデータに影響を与える場合、少なくともそれは私が行う方法です。
Darren Corbett

私はこの道を歩み始めました、そしてそれは単純なシナリオのためにうまく働きます。私も連鎖をシミュレートし、連鎖呼び出すための「キープ」/「ブレーク」ヘルパー提供モック作成gist.github.com/marknadig/c3e8f2d3fff9d22da42bを 、より複雑なシナリオでは、これはしかし、倒れます。私の場合、キャッシュからアイテムを(条件付きで)条件付きで返すか、要求を行うサービスがありました。だから、それはそれ自身の約束を作り出していました。
Mark Nadig

この投稿ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spyは、偽の "then"の使用方法について完全に説明しています。
Custodio

8

sinonのようなスタブライブラリを使用して、サービスをモックすることができます。その後、約束として$ q.when()を返すことができます。スコープオブジェクトの値がpromise結果からのものである場合は、scope。$ root。$ digest()を呼び出す必要があります。

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

正直なところ、モジュールの代わりにサービスをモックするためにインジェクトに依存することで、これを間違った方法で行っています。また、beforeEachでinjectを呼び出すと、テストごとにモックを作成することが難しくなるため、アンチパターンです。

これが私がこれを行う方法です...

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
before beforeの全体的なポイントは、各テストの前に呼び出されることです。テストの記述方法はわかりませんが、個人的には単一の関数に対して複数のテストを記述します。したがって、前に呼び出される共通のベースセットアップがあります。各テスト。また、ソフトウェアエンジニアリングに関連するアンチパターンの理解された意味を調べることもできます。
Darren Corbett、2015年

0

sinon.stub()。returns($ 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');
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.