web-dev-qa-db-fra.com

Injecter une maquette dans un service AngularJS

J'ai un service AngularJS écrit et je voudrais le tester.

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

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

    return this;
});

Mon fichier app.js a ces derniers enregistrés:

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

Je peux tester que la DI fonctionne comme telle:

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

Cela a prouvé que le service peut être créé par le cadre DI, mais je veux maintenant tester le service, ce qui implique de mocker les objets injectés.

Comment puis-je faire cela?

J'ai essayé de placer mes objets fictifs dans le module, par exemple.

beforeEach(module(mockNavigationService));

et en réécrivant la définition de service comme suit:

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

Mais ce dernier semble arrêter le service créé par le DI comme tout.

Est-ce que quelqu'un sait comment je peux me moquer des services injectés pour mes tests unitaires?

Merci

David

114
BanksySan

Vous pouvez injecter des simulacres dans votre service en utilisant $provide.

Si vous avez le service suivant avec une dépendance qui a une méthode appelée getSomething:

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

Vous pouvez injecter une version factice de myDependency comme suit:

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

});

Notez qu'en raison de l'appel à $provide.value vous n'avez pas réellement besoin d'injecter explicitement myDependency n'importe où. Cela se passe sous le capot lors de l'injection de myService. Lors de la configuration de mockDependency ici, cela pourrait tout aussi bien être un espion.

Merci à loyalBrown pour le lien vers cette superbe vidéo .

181
John Galambos

À mon avis, il n’est pas nécessaire de se moquer des services eux-mêmes. Il suffit de se moquer des fonctions sur le service. De cette façon, vous pouvez avoir angular injecter vos services réels comme dans l’application. Puis, simulez les fonctions du service à l'aide de la fonction spyOn de Jasmine.

Maintenant, si le service lui-même est une fonction et non un objet avec lequel vous pouvez utiliser spyOn, il existe un autre moyen de procéder. J'avais besoin de faire cela et j'ai trouvé quelque chose qui fonctionne assez bien pour moi. Voir Comment vous moquez Angular service qui est une fonction?

4
dnc253

En plus de réponse de John Galambos : si vous voulez simplement vous moquer de méthodes spécifiques d'un service, vous pouvez le faire comme ceci:

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

});
2
Ignitor

Une autre option pour faciliter les dépendances moqueuses dans Angular et Jasmine consiste à utiliser QuickMock. Il peut être trouvé sur GitHub et vous permet de créer des simulacres simples de manière réutilisable. Vous pouvez le cloner depuis GitHub via le lien ci-dessous. Le README est assez explicite, mais j'espère que cela pourrait aider les autres à l'avenir.

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

Il gère automatiquement tous les points chauds mentionnés ci-dessus, de sorte que vous n'avez pas à écrire tout le code d'injection factice dans chaque test. J'espère que ça t'as aidé.

2
tennisgent

Si votre contrôleur est écrit pour accepter une dépendance comme celle-ci:

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

alors vous pouvez créer un faux someDependency dans un test Jasmine comme ceci:

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

J'ai récemment publié ngImprovedTesting, qui devrait faciliter les tests fictifs de manière AngularJS.

Pour tester 'myService' (à partir du module "myApp") avec ses dépendances fooService et barService simulées, vous pouvez effectuer les opérations suivantes dans votre test Jasmine:

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

Pour plus d'informations sur ngImprovedTesting, consultez son article de blog d'introduction: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/ =

1
Emil van Galen

Je sais que c’est une vieille question, mais il existe un autre moyen plus simple, vous pouvez créer une maquette et désactiver l’original injecté lors d’une fonction donnée. Cela peut être fait en utilisant spyOn sur toutes les méthodes. voir le code ci-dessous.

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