今天來回顧這幾週學習的內容,用重點整理的方式,讓自己在短時間內抓到重點。
UserComponent
,再取得 debugElement 查詢 UI 有沒 data-test
的屬性<div data-test="login">
<h1>User logged in</h1>
<p>User is: {{ user.name }}</p>
</div>
describe('Component: User', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UserComponent]
});
});
it('should show login class', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
expect(fixture.nativeElement.querySelector('[data-test="login"]')).toBeTruthy();
});
})
<div *ngIf="isLoggedIn">
<h1>User logged in</h1>
<p>User is: {{ user.name }}</p>
</div>
<div *ngIf="!isLoggedIn">
<h1>User not logged in</h1>
<p>Please log in first</p>
</div>
export class UserService {
user = {
name: 'Max'
};
}
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
providers: [UserService, DataService]
})
export class UserComponent implements OnInit {
user: {name: string};
isLoggedIn = false;
data: string;
constructor(private userService: UserService, private dataService: DataService) { }
ngOnInit() {
this.user = this.userService.user;
this.dataService.getDetails().then((data: string) => this.data = data);
}
}
describe('Component: User', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UserComponent]
});
});
it('should create the app', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it('should use the user name from the service', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let userService = fixture.debugElement.injector.get(UserService);
fixture.detectChanges();
expect(userService.user.name).toEqual(app.user.name);
});
it('should display the user name if user is logged in', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
app.isLoggedIn = true;
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('p').textContent).toContain(app.user.name);
});
it('shouldn\'t display the user name if user is not logged in', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('p').textContent).not.toContain(app.user.name);
});
});
接續 Service 的 HTML, Typescript, 測試呼叫 dataService 同步異步的行為
export class DataService {
getDetails() {
const resultPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data');
}, 1500);
});
return resultPromise;
}
}
async
則會得到 undefined
結果async
取得fakeAsync
, tick
時光機,不用等待時間到才能取值 it('shouldn\'t fetch data successfully if not called asynchronously', () => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
expect(app.data).toBe(undefined);
});
it('should fetch data successfully if called asynchronously', async(() => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(app.data).toBe('Data');
});
}));
it('should fetch data successfully if called asynchronously', fakeAsync(() => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails')
.and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
tick();
expect(app.data).toBe('Data');
}));
把TestBed... Angular測試模組抽掉,也可測試工具導向的功能,以這次的範例是寫一個 pipe 反轉字串
import { Pipe } from "@angular/core";
@Pipe({
name: 'reverse'
})
export class ReversePipe {
transform(value: string) {
return value.split("").reverse().join("");
}
}
import { ReversePipe } from "./reverse.pipe";
describe('Pipe: ReversePipe', () => {
it('should reverse the inputs', () => {
let reversePipe = new ReversePipe();
expect(reversePipe.transform('hello')).toEqual('olleh');
});
});
這邊開始是 Neil 課程的重點
將所有的 Mock 資料都寫到一隻 testing-utils.ts
管理內容,在寫元件測試 spec 的時候,在 import configureTestingModule
,讓 TestBed 可以吃同一個設定
使用細節回顧 Day 14 如何用 configureTestingModule 設定 Unit Test 環境
用 spyOn xx元件後的 'doSomething' 要對應實際元件上的函式名字,測試案例可以測試:
(1) 取得 doSomething 函式後,有沒有呼叫到 loadData()
(2) 不呼叫的話,要加 not
it('should call a function during another function', () => {
//Assign
const spy = spyOn(component,'doSomething');
//Act
component.loadData();
//Assert
expect(spy).toHaveBeenCalledWith();
});
it('should not call a function during another function', () => {
//Assign
const spy = spyOn(component,'doSomething');
//Act
component.dontLoadData();
//Assert
expect(spy).not.toHaveBeenCalledWith();
});
(3) 非必要,不測試 private function,但還是要測的話,就把 private function 名稱當作 key 執行私有函式呼叫測試
it('should call a function during a private function', () => {
//Assign
const spy = spyOn(component,'insidePrivateFunction');
//Act
component['privateFunction']();
//Assert
expect(spy).toHaveBeenCalledWith();
});
總共有三個類型:
細節可以回顧 Angular TDD 測試從0到1: Day 16 Typescript Unit Test(2)