Iniettare un mock in un servizio AngularJS


114

Ho scritto un servizio AngularJS e vorrei testarlo.

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

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

    return this;
});

Il mio file app.js ha questi registrati:

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

Posso testare che DI funziona come tale:

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

Ciò ha dimostrato che il servizio può essere creato dal framework DI, tuttavia in seguito voglio testare l'unità del servizio, il che significa deridere gli oggetti iniettati.

Come procedo a fare questo?

Ho provato a inserire i miei oggetti fittizi nel modulo, ad es

beforeEach(module(mockNavigationService));

e riscrivendo la definizione del servizio come:

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

Ma quest'ultimo sembra fermare il servizio creato dal DI come tutti.

Qualcuno sa come posso deridere i servizi iniettati per i miei test unitari?

Grazie

David


Puoi dare un'occhiata a questa mia risposta ad un'altra domanda, spero possa esserti utile.
remigio

Risposte:


183

Puoi iniettare mock nel tuo servizio usando $provide .

Se hai il seguente servizio con una dipendenza che ha un metodo chiamato getSomething:

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

Puoi iniettare una versione fittizia di myDependency come segue:

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

});

Nota che a causa della chiamata a $provide.value non è effettivamente necessario iniettare esplicitamente myDependency da nessuna parte. Succede sotto il cofano durante l'iniezione di myService. Quando si imposta mockDependency qui, potrebbe facilmente essere una spia.

Grazie a loyalBrown per il link a quel fantastico video .


13
Funziona alla grande, ma attenzione a un dettaglio: la beforeEach(module('myModule'));chiamata DEVE arrivare prima della beforeEach(function () { MOCKING })chiamata, altrimenti i mock verranno sovrascritti dai servizi reali!
Nikos Paraskevopoulos

1
C'è un modo per deridere non il servizio ma costante nello stesso modo?
Artem

5
Simile al commento di Nikos, tutte le $providechiamate devono essere effettuate prima di utilizzare $injectoraltrimenti, riceverai un errore:Injector already created, can not register a module!
Provvencemac

7
E se la tua simulazione avesse bisogno di $ q? Quindi non puoi iniettare $ q nel mock prima di chiamare module () per registrare il mock. qualche idea?
Jake

4
Se stai usando coffeescript e vedi Error: [ng:areq] Argument 'fn' is not a function, got Object, assicurati di mettere un returnsulla riga dopo $provide.value(...). Il ritorno implicito ha $provide.value(...)causato quell'errore per me.
yndolok

4

Per come la vedo io, non c'è bisogno di deridere i servizi stessi. Semplicemente deridi le funzioni del servizio. In questo modo, puoi avere angolare iniettare i tuoi servizi reali come fa in tutta l'app. Quindi, simula le funzioni sul servizio secondo necessità usando la spyOnfunzione di Jasmine .

Ora, se il servizio stesso è una funzione e non un oggetto che puoi usare spyOncon, c'è un altro modo per farlo. Avevo bisogno di farlo e ho trovato qualcosa che funziona abbastanza bene per me. Vedi Come prendi in giro il servizio angolare che è una funzione?


3
Non credo che questo risponda alla domanda. E se la fabbrica del servizio che viene derisa fa qualcosa di non banale, come colpire il server per i dati? Sarebbe un buon motivo per volerlo deridere. Vuoi evitare la chiamata al server e invece creare una versione fittizia del servizio con dati falsi. Non è nemmeno una buona soluzione prendere in giro $ http, perché in realtà stai testando due servizi in un test, invece di testare singolarmente i due servizi. Quindi vorrei ripetere la domanda. Come si passa un servizio fittizio a un altro servizio in uno unit test?
Patrick Arnesen

1
Se sei preoccupato per il servizio che colpisce il server per i dati, è a questo che serve $ httpBackend ( docs.angularjs.org/api/ngMock.$httpBackend ). Non sono sicuro di cos'altro potrebbe essere una preoccupazione nella fabbrica del servizio che richiederebbe una presa in giro dell'intero servizio.
dnc253

2

Un'altra opzione per semplificare la simulazione delle dipendenze in Angular e Jasmine è usare QuickMock. Può essere trovato su GitHub e ti consente di creare semplici mock in modo riutilizzabile. Puoi clonarlo da GitHub tramite il link sottostante. Il README è abbastanza autoesplicativo, ma si spera che possa aiutare altri in futuro.

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

Gestisce automaticamente tutto il boilerplate sopra menzionato, quindi non devi scrivere tutto quel codice di iniezione fittizia in ogni test. Spero che aiuti.


2

Oltre alla risposta di John Galambos : se vuoi solo deridere metodi specifici di un servizio, puoi farlo in questo modo:

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

Se il tuo controller è stato scritto per accettare una dipendenza come questa:

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

quindi puoi fare un falso someDependencyin un test Jasmine come questo:

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
La domanda riguarda i servizi, che non vengono istanziati nella suite di test con una chiamata a un servizio equivalente come $ controller. In altre parole, non si chiama $ service () in un blocco beforeEach, passando le dipendenze.
Morris Singer

1

Recentemente ho rilasciato ngImprovedTesting che dovrebbe semplificare i test fittizi in AngularJS.

Per testare "myService" (dal modulo "myApp") con le sue dipendenze fooService e barService ridicolizzate, puoi eseguire le seguenti operazioni nel tuo test Jasmine:

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

Per ulteriori informazioni su ngImprovedTesting, consulta il suo post sul blog introduttivo: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
Perché è stato votato negativamente? Non capisco il valore del voto negativo senza un commento.
Jacob Brewer

0

So che questa è una vecchia domanda ma c'è un altro modo più semplice, puoi creare mock e disabilitare l'originale iniettato in una funzione, può essere fatto usando spyOn su tutti i metodi. vedere il codice sotto.

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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.