【问题标题】:Injecting dependent services when unit testing AngularJS services在单元测试 AngularJS 服务时注入依赖服务
【发布时间】:2012-12-23 16:38:27
【问题描述】:

我正在测试服务 A,但服务 A 依赖于服务 B(即服务 B 被注入服务 A)。

我见过this question,但我的情况有点不同,因为在我看来,mock 服务 B 而不是注入服务 B 的实际实例更有意义。我会模拟它和茉莉花间谍。

这是一个示例测试:

describe("Sample Test Suite", function() {

  beforeEach(function() {

    module('moduleThatContainsServiceA');

    inject([
      'serviceA', function(service) {
        this.service = service;
      }
    ]);

  });

  it('can create an instance of the service', function() {
    expect(this.service).toBeDefined();
  });
});

我得到的错误是:

错误:未知提供者:serviceBProvider

我怎么能做这样的事情?

【问题讨论】:

标签: javascript unit-testing angularjs jasmine karma-runner


【解决方案1】:

实际上在 AngularJS 依赖注入中使用“最后胜利”规则。因此,您可以在包含模块和依赖项之后在测试中定义您的服务,然后当您正在测试的服务 A 将使用 DI 请求服务 B 时,AngularJS 将提供服务 B 的模拟版本。

这通常是通过定义像 MyAppMocks 这样的新模块,将模拟的服务/值放在那里,然后将该模块添加为依赖项来完成的。

种类(示意图):

beforeEach(function() {
  angular.module('MyAppMocks',[]).service('B', ...));
  angular.module('Test',['MyApp','MyAppMocks']);
  ...

【讨论】:

  • 你刚刚救了我的命! :D 我试图将一个模拟服务注入另一个依赖它的服务中,但只有测试会使用模拟版本,而不是注入的服务。现在我有一个单独的模拟模块,我在覆盖所需服务的应用程序模块之后加载它。像魅力一样工作!
  • Thomas,你能分享一些关于你的解决方案的细节吗?我有 2 个模块,每个模块都包含一个服务,第一个模块的 service_1 被注入到第二个模块的 service_2 中。我正在使用 service_1 创建一个模拟模块,它应该覆盖原始 service_1。而且它被覆盖了,但只是在测试中,所以当我调用service_2时,它仍然在使用原来的service_1。
  • 我如何处理adding mocks to services
  • 虽然我确信这个答案是正确的,但我对“...”有点困惑。我在想在一个依赖于“MyApp”的模块中测试一个控制器的上下文中考虑这个。 ……会发生什么?我现在在测试“测试”模块的功能吗?
  • @clearf in "..." 你定义了服务'B'的模拟,它为要测试的控制器提供必要的功能。因此,基本上,您通过创建间谍(例如)仅实现服务 B 的一部分,并确保在测试结束时调用了所有需要的服务“B”方法。
【解决方案2】:

我发现最简单的方法就是注入服务 B 并模拟它。例如服务车取决于服务引擎。现在我们需要在测试 Car 时模拟 Engine:

describe('Testing a car', function() {
      var testEngine;

  beforeEach(module('plunker'));
  beforeEach(inject(function(engine){
    testEngine = engine;
  }));

  it('should drive slow with a slow engine', inject(function(car) {
    spyOn(testEngine, 'speed').andReturn('slow');
    expect(car.drive()).toEqual('Driving: slow');
  }));
});

参考:https://github.com/angular/angular.js/issues/1635

【讨论】:

    【解决方案3】:

    这对我有用。关键是定义要模拟的真实模块。调用 angular.mock.module 使真正的模块可模拟并允许连接。

        beforeEach( ->
            @weather_service_url = '/weather_service_url'
            @weather_provider_url = '/weather_provider_url'
            @weather_provider_image = "test.jpeg"
            @http_ret = 'http_works'
            module = angular.module('mockModule',[])
            module.value('weather_service_url', @weather_service_url)
            module.value('weather_provider_url', @weather_provider_url)
            module.value('weather_provider_image', @weather_provider_image)
            module.service('weather_bug_service', services.WeatherBugService)
    
            angular.mock.module('mockModule')
    
            inject( ($httpBackend,weather_bug_service) =>
                @$httpBackend = $httpBackend
                @$httpBackend.when('GET', @weather_service_url).respond(@http_ret)
                @subject = weather_bug_service
            )
        )
    

    【讨论】:

      【解决方案4】:

      我在 CoffeeScript 中执行此操作并发现了一个额外的问题。 (此外,我发现此页面上的代码简洁得令人困惑。)这是一个完整的工作示例:

      describe 'serviceA', ->
         mockServiceB = {}
      
         beforeEach module 'myApp' # (or just 'myApp.services')
      
         beforeEach ->
            angular.mock.module ($provide) ->
               $provide.value 'serviceB', mockServiceB
               null
      
         serviceA = null
         beforeEach inject ($injector) ->
            serviceA = $injector.get 'serviceA'
      
         it 'should work', ->
            expect( true ).toBe( true )
            #serviceA.doStuff()
      

      $provide.value 之后没有显式返回null,我一直得到Error: Argument 'fn' is not a function, got Object。我在Google Groups thread 中找到了答案。

      【讨论】:

      • 尝试使用空的return 而不是null。这样,您生成的 javascript 将不会在函数末尾有额外的 return null 行。相反,您的 beforeEach 函数不会返回任何内容。
      • 我目前正在尝试使用类似的代码,但如果我使用您的代码,我会收到 SyntaxError: Unexpecte string 'serviceA'。关于如何解决这个问题的任何想法? -- 也使用 Coffeescript
      • @MathieuBrouwers,您可能应该提出一个新问题。我不确定你到底指的是什么,我必须看到你的代码才能回答。
      • 我现在发现了问题,是在我尝试实际测试 CoffeeScript 而不是编译为 javascript 代码的时候。我想我的评论有点没用,现在可以删除。
      【解决方案5】:

      Valentyn 解决方案对我有用,但还有另一种选择。

      beforeEach(function () {
      
          angular.mock.module("moduleThatContainsServiceA", function ($provide) {
                      $provide.value('B', ...);
                  });
      });
      

      然后当 AngularJS 服务 A 通过依赖注入请求服务 B 时,将提供服务 B 的模拟,而不是来自 moduleThatContainsServiceA 的服务 B。

      这样你就不需要创建一个额外的角度模块来模拟一个服务。

      【讨论】:

      • 优秀。在我的情况下,“...”被替换为“{}”。 Bam,完全删除了一个依赖项。
      • 这个问题是你仍然在测试中模拟你的服务。假设您需要在几个地方模拟这个并且发生了一些变化。然后,您将不得不更改每个文件,而不是在一个地方进行更改。为了可维护性,Valentyns 的答案应该是公认的答案(确实如此)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-11
      • 2017-11-19
      • 1970-01-01
      • 2012-05-20
      • 1970-01-01
      • 2016-01-26
      • 2019-04-12
      相关资源
      最近更新 更多