W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
為了檢查你的服務(wù)是否正常工作,你可以專門(mén)為它們編寫(xiě)測(cè)試。
如果你要試驗(yàn)本指南中所講的應(yīng)用,請(qǐng)在瀏覽器中運(yùn)行它或下載并在本地運(yùn)行它。
服務(wù)往往是最容易進(jìn)行單元測(cè)試的文件。下面是一些針對(duì) ?ValueService
?的同步和異步單元測(cè)試,甚至不需要 Angular 測(cè)試工具的幫助。
// Straight Jasmine testing without Angular's testing support
describe('ValueService', () => {
let service: ValueService;
beforeEach(() => { service = new ValueService(); });
it('#getValue should return real value', () => {
expect(service.getValue()).toBe('real value');
});
it('#getObservableValue should return value from observable',
(done: DoneFn) => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
done();
});
});
it('#getPromiseValue should return value from a promise',
(done: DoneFn) => {
service.getPromiseValue().then(value => {
expect(value).toBe('promise value');
done();
});
});
});
服務(wù)通常依賴于 Angular 在構(gòu)造函數(shù)中注入的其它服務(wù)。在很多情況下,調(diào)用服務(wù)的構(gòu)造函數(shù)時(shí),很容易手動(dòng)創(chuàng)建和注入這些依賴。
?MasterService
?就是一個(gè)簡(jiǎn)單的例子:
@Injectable()
export class MasterService {
constructor(private valueService: ValueService) { }
getValue() { return this.valueService.getValue(); }
}
?MasterService
?只把它唯一的方法 ?getValue
?委托給了所注入的 ?ValueService
?。
這里有幾種測(cè)試方法。
describe('MasterService without Angular testing support', () => {
let masterService: MasterService;
it('#getValue should return real value from the real service', () => {
masterService = new MasterService(new ValueService());
expect(masterService.getValue()).toBe('real value');
});
it('#getValue should return faked value from a fakeService', () => {
masterService = new MasterService(new FakeValueService());
expect(masterService.getValue()).toBe('faked service value');
});
it('#getValue should return faked value from a fake object', () => {
const fake = { getValue: () => 'fake value' };
masterService = new MasterService(fake as ValueService);
expect(masterService.getValue()).toBe('fake value');
});
it('#getValue should return stubbed value from a spy', () => {
// create `getValue` spy on an object representing the ValueService
const valueServiceSpy =
jasmine.createSpyObj('ValueService', ['getValue']);
// set the value to return when the `getValue` spy is called.
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
masterService = new MasterService(valueServiceSpy);
expect(masterService.getValue())
.withContext('service returned stub value')
.toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
.toBe(stubValue);
});
});
第一個(gè)測(cè)試使用 ?new
?創(chuàng)建了一個(gè) ?ValueService
?,并把它傳給了 ?MasterService
?的構(gòu)造函數(shù)。
然而,注入真實(shí)服務(wù)很難工作良好,因?yàn)榇蠖鄶?shù)被依賴的服務(wù)都很難創(chuàng)建和控制。
相反,可以模擬依賴、使用仿制品,或者在相關(guān)的服務(wù)方法上創(chuàng)建一個(gè)測(cè)試間諜。
我更喜歡用測(cè)試間諜,因?yàn)樗鼈兺ǔJ悄M服務(wù)的最佳途徑。
這些標(biāo)準(zhǔn)的測(cè)試技巧非常適合對(duì)服務(wù)進(jìn)行單獨(dú)測(cè)試。
但是,你幾乎總是使用 Angular 依賴注入機(jī)制來(lái)將服務(wù)注入到應(yīng)用類中,你應(yīng)該有一些測(cè)試來(lái)體現(xiàn)這種使用模式。Angular 測(cè)試實(shí)用工具可以讓你輕松調(diào)查這些注入服務(wù)的行為。
你的應(yīng)用依靠 Angular 的依賴注入(DI)來(lái)創(chuàng)建服務(wù)。當(dāng)服務(wù)有依賴時(shí),DI 會(huì)查找或創(chuàng)建這些被依賴的服務(wù)。如果該被依賴的服務(wù)還有自己的依賴,DI 也會(huì)查找或創(chuàng)建它們。
作為服務(wù)的消費(fèi)者,你不應(yīng)該關(guān)心這些。你不應(yīng)該關(guān)心構(gòu)造函數(shù)參數(shù)的順序或它們是如何創(chuàng)建的。
作為服務(wù)的測(cè)試人員,你至少要考慮第一層的服務(wù)依賴,但當(dāng)你用 ?TestBed
?測(cè)試實(shí)用工具來(lái)提供和創(chuàng)建服務(wù)時(shí),你可以讓 Angular DI 來(lái)創(chuàng)建服務(wù)并處理構(gòu)造函數(shù)的參數(shù)順序。
?TestBed
?是 Angular 測(cè)試實(shí)用工具中最重要的。?TestBed
?創(chuàng)建了一個(gè)動(dòng)態(tài)構(gòu)造的 Angular 測(cè)試模塊,用來(lái)模擬一個(gè) Angular 的 ?@NgModule
?。
?TestBed.configureTestingModule()
? 方法接受一個(gè)元數(shù)據(jù)對(duì)象,它可以擁有?@NgModule
?的大部分屬性。
要測(cè)試某個(gè)服務(wù),你可以在元數(shù)據(jù)屬性 ?providers
?中設(shè)置一個(gè)要測(cè)試或模擬的服務(wù)數(shù)組。
let service: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({ providers: [ValueService] });
});
將服務(wù)類作為參數(shù)調(diào)用 ?TestBed.inject()
?,將它注入到測(cè)試中。
注意:
?TestBed.get()
? 已在 Angular 9 中棄用。為了幫助減少重大變更,Angular 引入了一個(gè)名為 ?TestBed.inject()
? 的新函數(shù),你可以改用它。
it('should use ValueService', () => {
service = TestBed.inject(ValueService);
expect(service.getValue()).toBe('real value');
});
或者,如果你喜歡把這個(gè)服務(wù)作為設(shè)置代碼的一部分進(jìn)行注入,也可以在 ?beforeEach()
? 中做。
beforeEach(() => {
TestBed.configureTestingModule({ providers: [ValueService] });
service = TestBed.inject(ValueService);
});
測(cè)試帶依賴的服務(wù)時(shí),需要在 ?providers
?數(shù)組中提供 mock。
在下面的例子中,mock 是一個(gè)間諜對(duì)象。
let masterService: MasterService;
let valueServiceSpy: jasmine.SpyObj<ValueService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('ValueService', ['getValue']);
TestBed.configureTestingModule({
// Provide both the service-to-test and its (spy) dependency
providers: [
MasterService,
{ provide: ValueService, useValue: spy }
]
});
// Inject both the service-to-test and its (spy) dependency
masterService = TestBed.inject(MasterService);
valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;
});
該測(cè)試會(huì)像以前一樣使用該間諜。
it('#getValue should return stubbed value from a spy', () => {
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
expect(masterService.getValue())
.withContext('service returned stub value')
.toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
.toBe(stubValue);
});
本指南中的大多數(shù)測(cè)試套件都會(huì)調(diào)用 ?beforeEach()
? 來(lái)為每一個(gè) ?it()
? 測(cè)試設(shè)置前置條件,并依賴 ?TestBed
?來(lái)創(chuàng)建類和注入服務(wù)。
還有另一種測(cè)試,它們從不調(diào)用 ?beforeEach()
?,而是更喜歡顯式地創(chuàng)建類,而不是使用 ?TestBed
?。
你可以用這種風(fēng)格重寫(xiě) ?MasterService
?中的一個(gè)測(cè)試。
首先,在 setup 函數(shù)中放入可供復(fù)用的預(yù)備代碼,而不用 ?beforeEach()
?。
function setup() {
const valueServiceSpy =
jasmine.createSpyObj('ValueService', ['getValue']);
const stubValue = 'stub value';
const masterService = new MasterService(valueServiceSpy);
valueServiceSpy.getValue.and.returnValue(stubValue);
return { masterService, stubValue, valueServiceSpy };
}
?setup()
? 函數(shù)返回一個(gè)包含測(cè)試可能引用的變量(如 ?masterService
?)的對(duì)象字面量。你并沒(méi)有在 ?describe()
? 的函數(shù)體中定義半全局變量(比如 ?let masterService: MasterService
?)。
然后,每個(gè)測(cè)試都會(huì)在第一行調(diào)用 ?setup()
?,然后繼續(xù)執(zhí)行那些操縱被測(cè)主體和斷言期望值的步驟。
it('#getValue should return stubbed value from a spy', () => {
const { masterService, stubValue, valueServiceSpy } = setup();
expect(masterService.getValue())
.withContext('service returned stub value')
.toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
.toBe(stubValue);
});
請(qǐng)注意測(cè)試如何使用解構(gòu)賦值來(lái)提取它需要的設(shè)置變量。
const { masterService, stubValue, valueServiceSpy } = setup();
許多開(kāi)發(fā)人員都覺(jué)得這種方法比傳統(tǒng)的 ?beforeEach()
? 風(fēng)格更清晰明了。
雖然這個(gè)測(cè)試指南遵循傳統(tǒng)的樣式,并且默認(rèn)的CLI 原理圖會(huì)生成帶有 ?beforeEach()
? 和 ?TestBed
?的測(cè)試文件,但你可以在自己的項(xiàng)目中采用這種替代方式。
對(duì)遠(yuǎn)程服務(wù)器進(jìn)行 HTTP 調(diào)用的數(shù)據(jù)服務(wù)通常會(huì)注入并委托給 Angular 的 ?HttpClient
?服務(wù)進(jìn)行 XHR 調(diào)用。
你可以測(cè)試一個(gè)注入了 ?HttpClient
?間諜的數(shù)據(jù)服務(wù),就像測(cè)試所有帶依賴的服務(wù)一樣。
let httpClientSpy: jasmine.SpyObj<HttpClient>;
let heroService: HeroService;
beforeEach(() => {
// TODO: spy on other methods too
httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
heroService = new HeroService(httpClientSpy);
});
it('should return expected heroes (HttpClient called once)', (done: DoneFn) => {
const expectedHeroes: Hero[] =
[{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
httpClientSpy.get.and.returnValue(asyncData(expectedHeroes));
heroService.getHeroes().subscribe({
next: heroes => {
expect(heroes)
.withContext('expected heroes')
.toEqual(expectedHeroes);
done();
},
error: done.fail
});
expect(httpClientSpy.get.calls.count())
.withContext('one call')
.toBe(1);
});
it('should return an error when the server returns a 404', (done: DoneFn) => {
const errorResponse = new HttpErrorResponse({
error: 'test 404 error',
status: 404, statusText: 'Not Found'
});
httpClientSpy.get.and.returnValue(asyncError(errorResponse));
heroService.getHeroes().subscribe({
next: heroes => done.fail('expected an error, not heroes'),
error: error => {
expect(error.message).toContain('test 404 error');
done();
}
});
});
?HeroService
?方法會(huì)返回 ?Observables
?。你必須訂閱一個(gè)可觀察對(duì)象(a)讓它執(zhí)行,(b)斷言該方法成功或失敗。
?subscribe()
? 方法會(huì)接受成功(?next
?)和失敗(?error
?)回調(diào)。確保你會(huì)同時(shí)提供這兩個(gè)回調(diào)函數(shù),以便捕獲錯(cuò)誤。如果不這樣做就會(huì)產(chǎn)生一個(gè)異步的、沒(méi)有被捕獲的可觀察對(duì)象的錯(cuò)誤,測(cè)試運(yùn)行器可能會(huì)把它歸因于一個(gè)完全不相關(guān)的測(cè)試。
數(shù)據(jù)服務(wù)和 ?HttpClient
?之間的擴(kuò)展交互可能比較復(fù)雜,并且難以通過(guò)間諜進(jìn)行模擬。
?HttpClientTestingModule
?可以讓這些測(cè)試場(chǎng)景更易于管理。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: