将模拟注入AngularJS服务

时间:2013-02-08 13:06:59

标签: javascript angularjs mocking jasmine angularjs-service

我写了一个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所创建的服务。

有人知道如何为我的单元测试模拟注入的服务吗?

由于

大卫

7 个答案:

答案 0 :(得分:180)

您可以使用$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。它在我的服务注入期间发生在引擎盖下。在这里设置mockDependency时,它可能很容易成为间谍。

感谢loyalBrown指向that great video的链接。

答案 1 :(得分:4)

我看待它的方式,没有必要自己模拟服务。只需模拟服务上的功能。这样,您可以像在整个应用程序中一样注入角度注入真实服务。然后,使用Jasmine的spyOn函数模拟服务上的函数。

现在,如果服务本身是一个函数,而不是一个可以使用spyOn的对象,那么还有另一种方法可以解决它。我需要做到这一点,找到适合我的东西。见How do you mock Angular service that is a function?

答案 2 :(得分:2)

在Angular和Jasmine中帮助简化模拟依赖项的另一个选项是使用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
        });
    });
    ....

它自动管理上面提到的所有样板文件,因此您不必在每个测试中写出所有模拟注入代码。希望有所帮助。

答案 3 :(得分:2)

John Galambos' answer之外:如果您只想模拟服务的特定方法,可以这样做:

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

});

答案 4 :(得分:1)

如果编写控制器以接受这样的依赖:

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

然后你可以在Jasmine测试中制作一个假的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();
    }));
});

答案 5 :(得分:1)

我最近发布了ngImprovedTesting,它可以让AngularJS中的模拟测试变得更容易。

测试我的服务' (来自" myApp"模块)及其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/

答案 6 :(得分:0)

我知道这是一个老问题,但还有另一种更简单的方法,你可以创建模拟并禁用在一个函数注入的原始,它可以通过在所有方法上使用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;
    }