Menyuntikkan tiruan ke layanan AngularJS

114

Saya memiliki layanan AngularJS tertulis dan saya ingin mengujinya.

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

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

    return this;
});

File app.js saya terdaftar sebagai berikut:

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

Saya dapat menguji DI bekerja seperti itu:

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

Ini membuktikan bahwa layanan dapat dibuat oleh kerangka DI, namun selanjutnya saya ingin menguji layanan unit, yang berarti mengejek objek yang diinjeksi.

Bagaimana cara saya melakukan ini?

Saya sudah mencoba memasukkan objek tiruan saya ke dalam modul, misalnya

beforeEach(module(mockNavigationService));

dan menulis ulang definisi layanan sebagai:

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

Tapi yang terakhir tampaknya menghentikan layanan yang dibuat oleh DI seperti semua.

Adakah yang tahu bagaimana saya bisa mengejek layanan yang disuntikkan untuk tes unit saya?

Terima kasih

David

BanksySan
sumber
Anda dapat melihat pada ini jawaban saya untuk pertanyaan lain, saya berharap itu bisa membantu Anda.
remigio

Jawaban:

183

Anda dapat menyuntikkan ejekan ke layanan Anda dengan menggunakan $provide.

Jika Anda memiliki layanan berikut dengan dependensi yang memiliki metode yang disebut getSomething:

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

Anda dapat memasukkan versi tiruan myDependency sebagai berikut:

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

});

Perhatikan bahwa karena panggilan ke $provide.valueAnda sebenarnya tidak perlu memasukkan myDependency secara eksplisit di mana pun. Itu terjadi di bawah kap selama injeksi myService. Saat menyiapkan mockDependency di sini, itu bisa dengan mudah menjadi mata-mata.

Terima kasih kepada loyalBrown atas tautan ke video hebat itu .

John Galambos
sumber
13
Berfungsi bagus, tetapi berhati-hatilah dengan detail: beforeEach(module('myModule'));panggilan HARUS datang sebelum beforeEach(function () { MOCKING })panggilan, atau ejekan akan ditimpa oleh layanan yang sebenarnya!
Nikos Paraskevopoulos
1
Adakah cara untuk mengejek bukan layanan tetapi konstan dengan cara yang sama?
Artem
5
Mirip dengan komentar Nikos, $providepanggilan apa pun harus dilakukan sebelum menggunakan $injector, jika tidak, Anda akan menerima kesalahan:Injector already created, can not register a module!
providerencemac
7
Bagaimana jika tiruan Anda membutuhkan $ q? Maka Anda tidak dapat memasukkan $ q ke dalam tiruan sebelum memanggil module () untuk mendaftarkan tiruan tersebut. Ada pemikiran?
Jake
4
Jika Anda menggunakan coffeescript dan Anda melihat Error: [ng:areq] Argument 'fn' is not a function, got Object, pastikan untuk memberi returnbaris setelahnya $provide.value(...). Kembali secara implisit $provide.value(...)menyebabkan kesalahan itu bagi saya.
yndolok
4

Cara saya melihatnya, tidak perlu mengejek layanan itu sendiri. Cukup tiru fungsi pada layanan. Dengan begitu, Anda dapat memiliki angular menyuntikkan layanan nyata Anda seperti halnya di seluruh aplikasi. Kemudian, tiru fungsi pada layanan sesuai kebutuhan menggunakan spyOnfungsi Jasmine .

Sekarang, jika layanan itu sendiri adalah sebuah fungsi, dan bukan objek yang dapat Anda gunakan spyOn, ada cara lain untuk melakukannya. Saya perlu melakukan ini, dan menemukan sesuatu yang bekerja cukup baik untuk saya. Lihat Bagaimana Anda mengejek layanan Angular yang merupakan fungsi?

dnc253
sumber
3
Saya tidak berpikir ini menjawab pertanyaan itu. Bagaimana jika pabrik layanan yang diejek melakukan sesuatu yang tidak sepele, seperti menekan server untuk mendapatkan data? Itu akan menjadi alasan yang bagus untuk mengejeknya. Anda ingin menghindari panggilan server, dan sebagai gantinya membuat versi tiruan layanan dengan data palsu. Mengolok-olok $ http juga bukan solusi yang baik, karena Anda sebenarnya menguji dua layanan dalam satu pengujian, alih-alih menguji unit dua layanan secara terpisah. Jadi saya ingin mengulangi pertanyaan itu. Bagaimana Anda meneruskan layanan tiruan ke layanan lain dalam pengujian unit?
Patrick Arnesen
1
Jika Anda khawatir tentang layanan yang mengenai server untuk data, itulah gunanya $ httpBackend ( docs.angularjs.org/api/ngMock.$httpBackend ). Saya tidak yakin apa lagi yang akan menjadi perhatian di pabrik layanan yang memerlukan ejekan seluruh layanan.
dnc253
2

Opsi lain untuk membantu membuat dependensi tiruan lebih mudah di Angular dan Jasmine adalah dengan menggunakan QuickMock. Ini dapat ditemukan di GitHub dan memungkinkan Anda membuat tiruan sederhana dengan cara yang dapat digunakan kembali. Anda dapat mengkloningnya dari GitHub melalui tautan di bawah ini. README cukup jelas, tapi semoga bisa membantu orang lain di masa depan.

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

Ini secara otomatis mengelola semua boilerplate yang disebutkan di atas, jadi Anda tidak perlu menulis semua kode injeksi tiruan itu di setiap pengujian. Semoga membantu.

pemain tenis
sumber
2

Selain jawaban John Galambos : jika Anda hanya ingin mengejek metode tertentu dari suatu layanan, Anda dapat melakukannya seperti ini:

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

});
Ignitor
sumber
1

Jika pengontrol Anda ditulis untuk menerima dependensi seperti ini:

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

maka Anda bisa membuat palsu someDependencydalam tes Jasmine seperti ini:

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();
    }));
});
CodingWithSpike
sumber
9
Pertanyaannya adalah tentang layanan, yang tidak dibuat instance-nya dalam rangkaian pengujian dengan panggilan ke layanan yang setara sebagai $ controller. Dengan kata lain, seseorang tidak memanggil $ service () di blok beforeEach, meneruskan dependensi.
Morris Singer
1

Saya baru-baru ini merilis ngImprovedTesting yang seharusnya membuat pengujian tiruan di AngularJS menjadi lebih mudah.

Untuk menguji 'myService' (dari modul "myApp") dengan fooService dan dependensi barService yang diejek, Anda dapat melakukan hal berikut dalam pengujian Jasmine Anda:

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

Untuk informasi lebih lanjut tentang ngImprovedTesting, lihat entri blog pengantar: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/

Emil van Galen
sumber
1
Mengapa ini ditolak? Saya tidak mengerti nilai down-voting tanpa komentar.
Jacob Brewer
0

Saya tahu ini adalah pertanyaan lama tetapi ada cara lain yang lebih mudah, Anda dapat membuat tiruan dan menonaktifkan injeksi asli pada satu fungsi, itu dapat dilakukan dengan menggunakan spyOn pada semua metode. lihat kode di bawah ini.

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;
    }
Gal Morad
sumber