jest提供覆蓋率報告等開發者所需要的所有測試工具,jest是一款幾乎零配置的 測試框架。angular jest單元測試的寫法為三步,引入測試內容,運行測試內容, 最后進行比較,是否達到預期。jest中的斷言使用expect, 它接受一個參數,就 是運行測試內容的結果,返回一個對象,這個對象來調用匹配器 (toBe) ,匹配器的參數就是我們的預期結果,這樣就可以對結果和預 期進行對比了,也就可以判斷對不對了。
兩個必會的方法test方法:Jest封裝的測試方法,一般填寫兩個參數,描述和測 試方法,expect方法 :預期方法,就是你調用了什么方法,傳遞了什么參數,得 到的預期是什么。
1 常用jest斷言
- toBe:絕對相等(
=)
// 在src/functions.js中創建被測試的模塊
export default {
sum(a, b) {
return a + b;
}
}
// 在test/functions.test.js文件中創建測試用例
import functions from '../src/functions';
test('sum(2 + 2) 等于 4', () => {
expect(functions.sum(2, 2)).toBe(4);
})
- not:允許你測試結果不等于某個值的情況
// 在src/functions.js中創建被測試的模塊
export default {
sum(a, b) {
return a + b;
}
}
// functions.test.js
import functions from '../src/functions'
test('sum(2, 2) 不等于 5', () => {
expect(functions.sum(2, 2)).not.toBe(5);
})
- toEqual:簡單類型絕對匹配;復雜類型內容結果的匹配
// functions.js
export default {
getAuthor() {
return {
name: 'LITANGHUI',
age: 24,
}
}
}
// functions.test.js
import functions from '../src/functions';
test('getAuthor()返回的對象深度相等', () => {
expect(functions.getAuthor()).toEqual(functions.getAuthor());
})
test('getAuthor()返回的對象內存地址不同', () => {
expect(functions.getAuthor()).not.toBe(functions.getAuthor());
})
- toHaveLength:方便的用來測試字符串和數組類型的長度是否滿足預期
// functions.js
export default {
getIntArray(num) {
if (!Number.isInteger(num)) {
throw Error('"getIntArray"只接受整數類型的參數');
}
let result = [];
for (let i = 0, len = num; i < len; i++) {
result.push(i);
}
return result;
}
}
// functions.test.js
import functions from '../src/functions';
test('getIntArray(3)返回的數組長度應該為3', () => {
expect(functions.getIntArray(3)).toHaveLength(3);
})
- toThorw:可能夠讓我們測試被測試方法是否按照預期拋出異常,但是在使用時 需要注意的是:我們必須使用一個函數將將被測試的函數做一個包裝,正如上 面getIntArrayWrapFn所做的那樣,否則會因為函數拋出導致該斷言失敗
// functions.test.js
import functions from '../src/functions';
test('getIntArray(3.3)應該拋出錯誤', () => {
function getIntArrayWrapFn() {
functions.getIntArray(3.3);
}
expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整數類型的參數');
mockImplementation})
- toBeNull():匹配null
- toBeUndefined():匹配undefined
- toBeDefined():匹配非undefined
- toBeTruthy():匹配轉化后為true
- toBeFalsy():匹配轉化后為false
- toBeGreaterThan():相當于大于號
- toBeLessThan():相當于小于號
- toBeGreaterThanOrEqual():相當于大于等于號
- toBeLessThanOrEqual():相當于小于等于號
- toBeCloseTo():解決js浮點錯誤
- toMatch(regExp/string):用正則表達式或者字符串匹配字符串片段
- toContain():匹配數組或者Set中的某一項
2 mock函數
進行單元測試時,要測試的內容依賴其他內容,比如異步請求,會依賴網絡,很 可能造成測試達不到效果。能不能把依賴變成可控的內容?這就用到mack。mack 就是把依賴替換成我們可控的內容,實現測試的內容和它的依賴項隔離。那怎么 才能實現mock呢?使用mack 函數。在jest中,當我們談論mack的時候,其實談 論的就是使用mack函數代替依賴。mack函數就是一個虛擬的或假的函數,所以對 它來說,最重要的就是實現依賴的全部功能,從而起到替換的作用。通常,mock 函數會提供以下三個功能,來實現替換:函數的調用捕獲,設置函數返回值,改 變原函數的實現。在jest 創建一個mock 函數最簡單的方法就是調用jest.fn() 方法。
2.1 函數的調用捕捉
捕獲調用指的是這個函數有沒有被調用,調用的參數是什么,返回值是什么,通 常用于測試回調函數,模擬真實的回調函數。
// functions.js
export default {
forEachFun: (array: any[], callback: Function) => {
array.forEach((i) => callback(i));
}
}
// functions.test.js
import functions from '../src/functions';
test('forEachFun調用每個方法', () => {
const mockFun = jest.fn();
const testArr = [1, 2];
functions.forEachFun(testArr, mockFun);
console.log(mockFun.mock);
// expect(mockFun.mock.calls.length).toBe(2);
expect(mockFun).toHaveBeenCalled();
expect(mockFun).toBeCalledTimes(2);
expect(mockFun).toBeCalledWith(1);
expect(mockFun).toBeCalledWith(2);
});
// mock函數mockFun屬性是一個對象,打印結果:
{
calls: [ [ 1 ], [ 2 ] ],
instances: [ undefined, undefined ],
invocationCallOrder: [ 1, 2 ],
results:[
{ type: 'return', value: undefined },
{ type: 'return', value: undefined }
]
}
calls 保存的就是調用狀態。calls 是一個數組,每一次的調用都組成數組的 一個元素,在這里調用了兩次,就有兩個元素。每一個元素又是一個數組,它 則表示的是函數調用時的參數,因為每次的調用都傳遞了一個參數給函數,所 以數組只有一項。如果有多個參數,數組就有多項,按照函數中的參數列表依 次排列。這時候,就可以做斷言,函數調用了幾次,就判斷 calls.length. expect(mockFun.mock.calls.length).toBe(2) 就是斷言函數 是不是調用了兩次。expcet(mockFun.mock.calls[0][0]) .toBe(1)就是斷言第 一次調用的時候傳遞的參數是不是1. 可能覺得麻煩了, 的確有點麻煩了,幸 好,jest 對函數的mock參數進行了簡單的封裝,提供了簡單的匹配器。
// 用來判斷mock函數是否被掉用過 toHaveBeenCalled()/toBeCalled() // 用來判斷mock函數調用過幾次 toHaveBeenCalledTimes(number)/toBeCalledTimes(number) // 用來判斷是否使用了特定參數調mock函數 toHaveBeenCalledWith(arg1,arg2,...)/toBeCalledWith(arg1,arg2,...)
有的時候,由于后端沒有開發好,或網絡問題,不想調用函數,直接獲取到函數 的返回值就可以了,比如異步函數, 直接返回一個Observable就好了,根本沒有 必要請求服務器。
test('設置函數返回值', () => {
// 普通返回
const mockFun = jest.fn();
mockFun.
const result: string = mockFun();
expect(mockFun).toBeCalledTimes(1);
expect(result).toEqual({name: 'shao'});
// observable返回
const observableFun = jest.fn();
observableFun.mockReturnValue(of({name: 'shao'}));
const observableResult: Observable<any> = observableFun();
observableResult.subscribe((res) => {
expect(res).toEqual({name: 'shao'});
});
});
2.2 使用spyOn間諜測試服務
服務往往是最容易進行單元測試的文件。下面是一些針對 ValueService 的同步 和異步單元測試,甚至不需要 Angular 測試工具的幫助。
// 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',
() => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
});
});
});
服務通常依賴于angular 在構造函數中注入的其它服務。在很多情況下,調用 服務的構造函數時,很容易手動創建和注入這些依賴。
// ValueService.ts
@Injectable({
providedIn: 'root'
})
export class ValueService {
value = 'real value';
getValue(): string { return this.value; }
setValue(value: string): void { this.value = value; }
getObservableValue(): Observable<string> { return of('observable value'); }
getPromiseValue(): Promise<string> { return Promise.resolve('promise value'); }
getObservableDelayValue(): Observable<string> {
return of('observable delay value').pipe(delay(10));
}
}
// ValueService.spec.ts
export class FakeValueService extends ValueService {
value = 'faked service value';
}
describe('MasterService without angular testing support', () => {
let masterService: MasterService;
it('#getValue 返回 service', () => {
masterService = new MasterService(new ValueService());
expect(masterService.getValue()).toBe('real value');
});
it('#getValue 使用 fakeServiece', () => {
masterService = new MasterService(new FakeValueService());
expect(masterService.getValue()).toBe('faked service value');
});
it('#getValue 使用 fake object', () => {
const fake = { getValue: () => 'fake value' };
masterService = new MasterService(fake as ValueService);
expect(masterService.getValue()).toBe('fake value');
});
it('#getValue 返回 by a spy', () => {
const valueService: ValueService = new ValueService();
jest.spyOn(valueService, 'getValue').mockReturnValueOnce('test');
masterService = new MasterService(valueService);
expect(masterService.getValue()).toBe('test');
});
});
第一個測試使用 new 創建了一個 ValueService,并把它傳給了 MasterService 的構造函數。然而,注入真實服務很難工作良好,因為大多數被依賴的服務都很 難創建和控制。還可以模擬依賴、使用仿制品,或者在相關的服務方法上創建一 個測試間諜spyOn。
2.3 angular TestBed
TestBed 是 Angular 測試實用工具中最重要的。 TestBed 創建了一個動態構造 的 Angular 測試模塊,用來模擬一個 Angular 的 @NgModule 。 TestBed.configureTestingModule() 方法接受一個元數據對象,它可以擁有 @NgModule的大部分屬性。要測試某個服務,你可以在元數據屬性 providers 中 設置一個要測試或模擬的服務數組。
let masterService: MasterService;
let valueService: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MasterService, ValueService
]
});
masterService = TestBed.inject(MasterService);
valueService = TestBed.inject(ValueService);
});
測試帶依賴的服務時,同時使用spyOn:
// value.service.ts
@Injectable()
export class MasterService {
constructor(private valueService: ValueService) { }
getValue(): string {
return this.valueService.getValue();
}
}
// value.service.spec.ts
describe('MasterService動態構建', () => {
let masterService: MasterService;
let valueService: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MasterService, ValueService
]
});
masterService = TestBed.inject(MasterService);
valueService = TestBed.inject(ValueService);
});
it('#getValue從spy返回', () => {
const stubValue = 'stub value';
jest.spyOn(valueService, 'getValue').mockReturnValue(stubValue);
expect(masterService.getValue()).toBe(stubValue);
expect(valueService.getValue).toBeCalledTimes(1);
window.console.log(valueService.getValue);
expect(valueService.getValue()).toBe(stubValue);
});
});
Created: 2021-10-24 Sun 22:26
浙公網安備 33010602011771號