2013-01-09 184 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