简体   繁体   English

将模拟注入AngularJS服务

[英]Injecting a mock into an AngularJS service

I have an AngularJS service written and I would like to unit test it. 我写了一个AngularJS服务,我想对它进行单元测试。

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

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

    return this;
});

My app.js file has these registered: 我的app.js文件已注册:

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

I can test the DI is working as such: 我可以测试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();
    });
});

This proved that the service can be created by the DI framework, however next I want to unit test the service, which means mocking out the injected objects. 这证明了服务可以由DI框架创建,但是接下来我想对服务进行单元测试,这意味着模拟注入的对象。

How do I go about doing this? 我该怎么做呢?

I've tried putting my mock objects in the module, eg 我已经尝试将模拟对象放在模块中,例如

beforeEach(module(mockNavigationService));

and rewriting the service definition as: 并将服务定义重写为:

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

But the latter seems to stop the service being created by the DI as all. 但后者似乎停止了由DI创建的服务。

Does anybody know how I can mock the injected services for my unit tests? 有人知道如何模拟我的单元测试的注入服务吗?

Thanks 谢谢

David 大卫

You can inject mocks into your service by using $provide . 您可以使用$provide为您的服务注入模拟。

If you have the following service with a dependency that has a method called getSomething: 如果您具有以下服务,其依赖项具有名为getSomething的方法:

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

You can inject a mock version of myDependency as follows: 您可以注入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');
  }));

});

Note that because of the call to $provide.value you don't actually need to explicitly inject myDependency anywhere. 请注意,由于调用$provide.value您实际上不需要在任何地方显式地注入myDependency。 It happens under the hood during the injection of myService. 它在我的服务注入期间发生在引擎盖下。 When setting up mockDependency here, it could just as easily be a spy. 在这里设置mockDependency时,它可能很容易成为间谍。

Thanks to loyalBrown for the link to that great video . 感谢loyalBrown链接到那个精彩的视频

The way I look at it, there's no need to mock the services themselves. 我看待它的方式,没有必要自己模拟服务。 Simply mock the functions on the service. 只需模拟服务上的功能。 That way, you can have angular inject your real services as it does throughout the app. 这样,您可以像在整个应用程序中一样注入角度注入真实服务。 Then, mock the functions on the service as needed using Jasmine's spyOn function. 然后,使用Jasmine的spyOn函数根据需要模拟服务上的函数。

Now, if the service itself is a function, and not an object that you can use spyOn with, there's another way to go about it. 现在,如果服务本身是一个函数,而不是一个可以使用spyOn的对象,那么还有另一种方法可以解决它。 I needed to do this, and found something that works pretty well for me. 我需要做到这一点,找到适合我的东西。 See How do you mock Angular service that is a function? 请参阅如何模拟作为函数的Angular服务?

Another option to help make mocking dependencies easier in Angular and Jasmine is to use QuickMock. 在Angular和Jasmine中帮助简化模拟依赖项的另一个选项是使用QuickMock。 It can be found on GitHub and allows you to create simple mocks in a reusable way. 它可以在GitHub上找到,并允许您以可重用的方式创建简单的模拟。 You can clone it from GitHub via the link below. 您可以通过下面的链接从GitHub克隆它。 The README is pretty self explanatory, but hopefully it might help others in the future. README非常自我解释,但希望它可能在将来帮助其他人。

https://github.com/tennisgent/QuickMock 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
        });
    });
    ....

It automatically manages all of the boilerplate mentioned above, so you don't have to write out all of that mock injection code in every test. 它自动管理上面提到的所有样板,因此您不必在每个测试中写出所有模拟注入代码。 Hope that helps. 希望有所帮助。

In addition to John Galambos' answer : if you just want to mock out specific methods of a service, you can do it like this: 除了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');
  }));

});

If your controller is written to take in a dependency like this: 如果您的控制器被编写为接受这样的依赖:

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

then you can make a fake someDependency in a Jasmine test like this: 然后你可以在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();
    }));
});

I recently released ngImprovedTesting that should make mock testing in AngularJS way easier. 我最近发布了ngImprovedTesting,它应该使AngularJS中的模拟测试更容易。

To test 'myService' (from the "myApp" module) with its fooService and barService dependencies mocked out you simple can do the following in in your Jasmine test: 要测试'myService'(来自“myApp”模块)并使用其fooService和barService依赖项进行模拟,您可以在Jasmine测试中执行以下操作:

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

For more information about ngImprovedTesting check out its introductory blog post: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/ 有关ngImprovedTesting的更多信息,请查看其介绍性博客文章: http ://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/

I know this is old question but there is another easier way ,you can create mock and disable the original injected at one function , it can be done by using spyOn on all the methods. 我知道这是一个老问题,但还有另一种更简单的方法,你可以创建模拟并禁用在一个函数注入的原始,它可以通过在所有方法上使用spyOn来完成。 see code below. 见下面的代码。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM