AngularJSサービスにモックを挿入する


114

AngularJSサービスを作成しており、それを単体テストしたいと思います。

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

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

    return this;
});

私のapp.jsファイルにはこれらが登録されています:

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

DIが機能していることをテストできます。

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

これにより、サービスをDIフレームワークで作成できることが証明されましたが、次にサービスの単体テストを行います。つまり、注入されたオブジェクトをモックアウトします。

これを行うにはどうすればよいですか?

私はモックオブジェクトをモジュールに入れてみました、例えば

beforeEach(module(mockNavigationService));

サービス定義を次のように書き換えます。

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

しかし、後者は、DIによって作成されているサービスをすべて停止するようです。

ユニットテストに注入されたサービスをモックする方法を誰かが知っていますか?

ありがとう

デビッド


あなたが見てとることができ、この別の質問に対する私の答えを、私はそれはあなたに役立つことができ願っています。
レミジオ2013

回答:


183

を使用して、サービスにモックを注入できます$provide

getSomethingというメソッドを持つ依存関係を持つ次のサービスがある場合:

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

次のようにして、myDependencyのモックバージョンを挿入できます。

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.value実際にはmyDependencyを明示的に注入する必要はありません。これは、myServiceの注入中に内部で発生します。ここでmockDependencyを設定すると、スパイと同じくらい簡単になり得ます。

すばらしいビデオへのリンクを提供してくれたloyalBrownに感謝します。


13
素晴らしい作品が、詳細を注意してください:beforeEach(module('myModule'));コールはHASの前に来るようにbeforeEach(function () { MOCKING })呼び出し、または他のモックは、実際のサービスによって上書きされます!
Nikos Paraskevopoulos 2014

1
同じ方法でサービスではなく定数を模擬する方法はありますか?
Artem 2015

5
ニコスはコメントと同様に、任意の$provide呼び出しを使用する前に行う必要があり$injectorそうでない場合は、エラーが表示されます:Injector already created, can not register a module!
providencemac

7
モックに$ qが必要な場合はどうなりますか?その後、モックを登録するためにmodule()を呼び出す前に$ qをモックに注入することはできません。何かご意見は?
ジェイク

4
coffeescriptを使用していてError: [ng:areq] Argument 'fn' is not a function, got Object、が表示されている場合は、のreturn後ろに必ずを付けてください$provide.value(...)。暗黙的に戻る$provide.value(...)と、そのエラーが発生しました。
yndolok

4

私の見方では、サービス自体をモックする必要はありません。単にサービスの機能を模擬します。このようにして、アプリ全体と同じように、実際のサービスを角度注入することができます。次に、必要に応じてJasmineのspyOn関数を使用して、サービスの関数をモックします。

ここで、サービス自体が関数であり、使用できるオブジェクトではない場合、それを回避するspyOn別の方法があります。私はこれを行う必要があり、私にとってかなりうまくいく何かを見つけました。関数であるAngularサービスどのようにモックするのかをご覧ください


3
これが質問に答えるとは思いません。モックされているサービスのファクトリーがサーバーにデータをヒットするなど、重要なことをしたらどうなるでしょうか。それは、それをあざけりたくなる正当な理由でしょう。サーバー呼び出しを回避し、代わりに偽のデータを含むサービスのモックバージョンを作成します。$ httpのモックは、2つのサービスを個別に単体テストするのではなく、1つのテストで実際に2つのサービスをテストするため、良い解決策でもありません。それで私は質問を繰り返します。単体テストでモックサービスを別のサービスにどのように渡しますか?
Patrick Arnesen 2013年

1
サービスがデータをサーバーにヒットすることを心配している場合は、それが$ httpBackendの目的です(docs.angularjs.org/api/ngMock.$httpBackend)。サービス全体をモックする必要があるサービスのファクトリーで他に何が問題になるかはわかりません。
dnc253 2013年

2

AngularとJasmineで依存関係のモックを容易にするもう1つのオプションは、QuickMockを使用することです。GitHubにあり、再利用可能な方法でシンプルなモックを作成できます。以下のリンクからGitHubからクローンできます。READMEはかなり自明ですが、うまくいけば将来的に他の人を助けるかもしれません。

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

上記のすべてのボイラープレートを自動的に管理するため、すべてのテストでそのモックインジェクションコードをすべて書き出す必要はありません。お役に立てば幸いです。


2

John Galambosの回答に加えて、サービスの特定のメソッドをモックアウトしたいだけの場合は、次のように実行できます。

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

コントローラが次のような依存関係を取り込むように記述されている場合:

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

その後、次のsomeDependencyようなジャスミンテストで偽物を作成できます。

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
問題は、$ controllerと同等のサービスを呼び出してテストスイートでインスタンス化されないサービスについてです。つまり、beforeEachブロックで$ service()を呼び出さずに、依存関係を渡します。
モリス歌手

1

私は最近AngularJSでの模擬テストをはるかに簡単にするngImprovedTestingをリリースしました。

「myApp」モジュールからの「myService」を、fooServiceおよびbarServiceの依存関係をモックアウトしてテストするには、Jasmineテストで次のように簡単に実行できます。

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

ngImprovedTestingの詳細についてはその入門ブログ記事をチェックアウト:http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
なぜこれが反対投票されたのですか?コメントなしで投票することの価値を理解できません。
Jacob Brewer

0

私はこれが古い質問であることを知っていますが、別の簡単な方法があります。モックを作成し、1つの関数で注入された元の関数を無効にすることができます。これは、すべてのメソッドでspyOnを使用して実行できます。以下のコードを参照してください。

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