2013-01-09 185 views
50

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

我见过this question但我的情况有点不同,因为在我看来,它更有意义的以模拟 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(); 
    }); 
}); 

我得到的错误是:

Error: Unknown provider: serviceBProvider

我怎么会做这样的事情?

+0

FWIW:我问过一个** QUnit版本**这个问题[这里CodeReview.SE]的(HTTP:/ /codereview.stackexchange.com/q/98519/10034)。 – Jeroen

回答

43

其实在AngularJS依赖注入使用'最后赢'的规则。因此,您可以在包含模块和依赖关系之后,在测试中定义您的服务,然后当您测试的服务A将使用DI请求服务B时,​​AngularJS将提供服务B的模拟版本。

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

的(示意图)种类:

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

你刚刚救了我的命!:D我在试图将一个模拟服务注入到依赖它的另一个服务中,但我只是在试图使用模拟版本而不是注入服务。现在我有一个单独的模拟模块,在应用模块覆盖所需服务之后加载。奇迹般有效! –

+3

托马斯,你能分享一些关于你的解决方案的细节吗?我有2个模块,每个模块都包含一个服务,第一个模块的service_1被注入到service_2的第二个模块中。我正在用service_1创建一个模拟模块,它应该覆盖原始服务_1。它被覆盖,但只在测试中,所以当我打电话给service_2时,它仍在使用原来的service_1。 –

+3

我如何处理[添加模拟服务](https://github.com/daniellmb/angular-test-patterns/blob/master/patterns/service.md#suggested-service-unit-test-setup-) – daniellmb

20

的Valentyn解决方案为我工作,但还有另外一种选择。

beforeEach(function() { 

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

然后当AngularJS服务的请求由依赖注入服务B,您的服务B的模拟将被提供,而不是从moduleThatContainsServiceA服务B。

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

+0

非常好。在我的情况下'...'被替换为'{}'。 Bam完全删除了一个依赖项。 – 2mia

+0

问题在于你仍然在测试中嘲笑你的服务。假设你需要在几个地方嘲笑这一点,并且改变了一些东西。那么你将不得不改变每个文件,而不是在一个地方改变。为了可维护性,Valentyns的答案应该是可接受的答案(它是)。 – perry

24

我在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找到了答案。

+1

尝试使用空的'return'而不是'null'。这样,你生成的javascript在函数结尾处不会有额外的'return null'行。相反,你的'beforeEach'函数不会返回任何内容。 – demisx

+0

我现在试图使用类似的代码,但是如果我使用你的代码,我得到一个'SyntaxError:Unexpecte字符串'serviceA''。任何想法如何解决这个问题? - 同样使用Coffeescript –

+0

@MathieuBrouwers,你应该可以开一个新的问题。我不确定你指的是什么,我必须看到你的代码才能回答。 – jab

1

这是我工作的。关键是定义一个真正的模块被嘲笑。调用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 
     ) 
    ) 
6

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

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