本節(jié)讓我們看看如何使用代碼的方式來完成點擊教師列表的動作,初步的接觸下自動化的魅力。
當(dāng)前測試用例下,我們需要模擬用戶點擊教師列表,以測試教師列表被點擊后,是否在組件的`@Output`上接收到了相應(yīng)的值。比如我們模擬點擊教師列表中的最后一個教師:
```typescript
+++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts
@@ -41,5 +41,10 @@ describe('KlassSelectComponent', () => {
fixture.autoDetectChanges();
component.beChange
.subscribe((data: number) => console.log('接收到了彈出的數(shù)據(jù)', data));
+ // 模擬點擊教師列表中的第二個教師
});
});
```
### 模擬點擊option
在響應(yīng)式表中,模擬點擊某個`option`僅需要以下兩步:
1. 獲取預(yù)點擊`option`對應(yīng)的值
2. 將值使用`setValue()`方法送入`FormControl`
```typescript
@@ -44,6 +42,7 @@ describe('KlassSelectComponent', () => {
component.beChange
.subscribe((data: number) => console.log('接收到了彈出的數(shù)據(jù)', data));
// 模擬點擊教師列表中的第二個教師
+ const teacher = component.teachers[1];
+ console.log(teacher);
});
});
```
打開控制臺我們得到了一個undefined:

產(chǎn)生該問題的原因從根上來講是由于JavaScript的異步機制。從當(dāng)前應(yīng)用層面上來講,是由于我們的MockApi模擬了真實網(wǎng)絡(luò)的延遲。當(dāng)前對組件的測試大概發(fā)生了以下事件:

如上圖示,由于`MockApi`的延遲發(fā)送數(shù)據(jù),所以在執(zhí)行到`const teacher = component.teachers[1];`時,`component.teachers`仍然是個空數(shù)組。
```typescript
// 模擬點擊教師列表中的第二個教師
+ console.log(component.teachers.length);
const teacher = component.teachers[1];
console.log(teacher);
```

但當(dāng)前我們希望的是單元測試按以下流程執(zhí)行:

## getTestScheduler()
預(yù)使在發(fā)生http請求時`MockApi`馬上發(fā)送數(shù)據(jù),則需要使用`jasmine-marbles`提供的`getTestScheduler()`,該方法的作用是:馬上發(fā)送(本計劃延遲發(fā)送的)數(shù)據(jù) 。由于`jasmine-marbles`只被應(yīng)用在測試環(huán)境下,所以我們使用`npm install --save-dev jasmine-marbles`來安裝:
```bash
panjie@panjies-Mac-Pro first-app % pwd
/Users/panjie/github/mengyunzhi/angular11-guild/first-app
panjie@panjies-Mac-Pro first-app % npm install --save-dev jasmine-marbles
...
+ jasmine-marbles@0.8.1
added 1 package, removed 1 package and audited 1472 packages in 9.079s
...
```
<hr>
**以下內(nèi)容了解即可**
實際上`MockApi`提供了兩種延遲發(fā)送數(shù)據(jù)的方案:`MockApiInterceptor`與`MockApiTestingInterceptor`。`MockApiInterceptor`用于沒有后臺的演示環(huán)境,該延遲是通過RxJS的`delay`方法實現(xiàn)的;而`MockApiTestingInterceptor`專門用于測試環(huán)境,該延遲是通的RxJS的彈珠來實現(xiàn)的。而彈珠測試的數(shù)據(jù)發(fā)送,是可以通過`jasmine-marbles`手動控制的。
**以下內(nèi)容了解即可**
<hr>
然后我們在當(dāng)前動態(tài)測試模塊下引入支持`jasmine-marbles`馬上發(fā)送數(shù)據(jù)的`MockApiTestingInterceptor`來替換原`MockApiInterceptor`:
```typescript
+++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts
-import {MockApiInterceptor} from '@yunzhi/ng-mock-api';
+import {MockApiTestingInterceptor} from '@yunzhi/ng-mock-api/testing';
@@ -22,7 +22,7 @@ describe('KlassSelectComponent', () => {
providers: [
{
provide: HTTP_INTERCEPTORS, multi: true,
- useClass: MockApiInterceptor.forRoot([
+ useClass: MockApiTestingInterceptor.forRoot([
TeacherMockApi
])
}
```
接下來便可以在測試代碼中調(diào)用`getTestScheduler()`方法來手動控制數(shù)據(jù)發(fā)送了:
```typescript
+++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts
@@ -5,6 +5,7 @@ import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {TeacherMockApi} from '../../mock-api/teacher.mock.api';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MockApiTestingInterceptor} from '@yunzhi/ng-mock-api/testing';
+import {getTestScheduler} from 'jasmine-marbles';
describe('KlassSelectComponent', () => {
let component: KlassSelectComponent;
@@ -41,6 +42,10 @@ describe('KlassSelectComponent', () => {
fixture.autoDetectChanges();
component.beChange
.subscribe((data: number) => console.log('接收到了彈出的數(shù)據(jù)', data));
+
+ // 手動控制MockApi發(fā)送數(shù)據(jù)
+ getTestScheduler().flush();
+
// 模擬點擊教師列表中的第二個教師
console.log(component.teachers.length);
```

需要**注意**的是:
1. 這種數(shù)據(jù)發(fā)送方式產(chǎn)生的數(shù)據(jù)變化并不會被`fixture`感知到,所以如果我們需要查看V層的效果,則需要在數(shù)據(jù)發(fā)送完成后,手動調(diào)用`detectChanges()`以使V層重新進行渲染。
2. 引入`MockApiTestingInterceptor`后,在測試中僅當(dāng)調(diào)用`getTestScheduler().flush();`時才會發(fā)送數(shù)據(jù),否則不會發(fā)送數(shù)據(jù)。
```typescript
+++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts
@@ -45,6 +45,7 @@ describe('KlassSelectComponent', () => {
// 手動控制MockApi發(fā)送數(shù)據(jù)
getTestScheduler().flush();
+ fixture.detectChanges();
// 模擬點擊教師列表中的第二個教師
console.log(component.teachers.length);
```
此時我們將在V層中將查看到教師列表中的兩個教師。
### setValue()
最后調(diào)用`FormControl`的`setValue()`完成`option`的模擬點擊:
```typescript
+++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts
@@ -51,5 +51,7 @@ describe('KlassSelectComponent', () => {
console.log(component.teachers.length);
const teacher = component.teachers[1];
console.log(teacher);
+
+ component.teacherId.setValue(teacher.id);
});
});
panjie
```

## 其它方式
我們還可以在單元測試中使用代碼的形式來模擬option的點擊(實際上Angular在select中的option點擊做的并不好),但此方法使用的代碼較多,難度相對較大,實際在生產(chǎn)中我們也很少使用。在這我們僅給出相應(yīng)的代碼供參考:
```typescript
fit('模擬點擊option', () => {
expect(component).toBeTruthy();
component.beChange
.subscribe((data: number) => console.log('接收到了彈出的數(shù)據(jù)', data));
// 手動控制MockApi發(fā)送數(shù)據(jù)
getTestScheduler().flush();
fixture.detectChanges();
// 獲取select
const htmlSelect = fixture.debugElement.query(By.css('select')).nativeElement as HTMLSelectElement;
htmlSelect.click();
// 獲取第二個選項
const htmlOption = htmlSelect.options[1];
// 用該選項的值設(shè)置select的值,從而實現(xiàn)模塊點擊
htmlSelect.value = htmlOption.value;
// 發(fā)送change事件,通知瀏覽器、angular、觀察者select值已經(jīng)發(fā)生了變化
htmlSelect.dispatchEvent(new Event('change'));
});
```
## 上結(jié)
本結(jié)我們使用`getTestScheduler()`方法控制了`MockApi`返回值的時機。這使得我們在脫離后臺的開發(fā)中,即可以決定http請求返回什么值,又可以決定其值在什么時候返回。這有利于我們掌握組件在http請求前后的狀態(tài)。
我們還調(diào)用了`formControl`的`setValue()`方法來快速的模擬用戶的點擊,這為以后使用代碼來完全代替不可靠的人工點擊打下了基礎(chǔ)。
| 名稱 | 鏈接 |
| -------- | ------------------------------------------------------------ |
| 本節(jié)源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.2.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.2.3.zip) |
- 序言
- 第一章 Hello World
- 1.1 環(huán)境安裝
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教師管理
- 2.1 教師列表
- 2.1.1 初始化原型
- 2.1.2 組件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 請求后臺數(shù)據(jù)
- 2.2.1 HttpClient
- 2.2.2 請求數(shù)據(jù)
- 2.2.3 模塊與依賴注入
- 2.2.4 異步與回調(diào)函數(shù)
- 2.2.5 集成測試
- 2.2.6 本章小節(jié)
- 2.3 新增教師
- 2.3.1 組件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 對接后臺
- 2.3.4 路由
- 2.4 編輯教師
- 2.4.1 組件初始化
- 2.4.2 獲取路由參數(shù)
- 2.4.3 插值與模板表達式
- 2.4.4 初識泛型
- 2.4.5 更新教師
- 2.4.6 測試中的路由
- 2.5 刪除教師
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome圖標(biāo)庫
- 2.6.3 firefox
- 2.7 總結(jié)
- 第三章 用戶登錄
- 3.1 初識單元測試
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 著陸組件
- 3.5 @Output
- 3.6 TypeScript 類
- 3.7 瀏覽器緩存
- 3.8 總結(jié)
- 第四章 個人中心
- 4.1 原型
- 4.2 管道
- 4.3 對接后臺
- 4.4 x-auth-token認證
- 4.5 攔截器
- 4.6 小結(jié)
- 第五章 系統(tǒng)菜單
- 5.1 延遲及測試
- 5.2 手動創(chuàng)建組件
- 5.3 隱藏測試信息
- 5.4 規(guī)劃路由
- 5.5 定義菜單
- 5.6 注銷
- 5.7 小結(jié)
- 第六章 班級管理
- 6.1 新增班級
- 6.1.1 組件初始化
- 6.1.2 MockApi 新建班級
- 6.1.3 ApiInterceptor
- 6.1.4 數(shù)據(jù)驗證
- 6.1.5 教師選擇列表
- 6.1.6 MockApi 教師列表
- 6.1.7 代碼重構(gòu)
- 6.1.8 小結(jié)
- 6.2 教師列表組件
- 6.2.1 初始化
- 6.2.2 響應(yīng)式表單
- 6.2.3 getTestScheduler()
- 6.2.4 應(yīng)用組件
- 6.2.5 小結(jié)
- 6.3 班級列表
- 6.3.1 原型設(shè)計
- 6.3.2 初始化分頁
- 6.3.3 MockApi
- 6.3.4 靜態(tài)分頁
- 6.3.5 動態(tài)分頁
- 6.3.6 @Input()
- 6.4 編輯班級
- 6.4.1 測試模塊
- 6.4.2 響應(yīng)式表單驗證
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定義FormControl
- 6.4.6 代碼重構(gòu)
- 6.4.7 小結(jié)
- 6.5 刪除班級
- 6.6 集成測試
- 6.6.1 惰性加載
- 6.6.2 API攔截器
- 6.6.3 路由與跳轉(zhuǎn)
- 6.6.4 ngStyle
- 6.7 初識Service
- 6.7.1 catchError
- 6.7.2 單例服務(wù)
- 6.7.3 單元測試
- 6.8 小結(jié)
- 第七章 學(xué)生管理
- 7.1 班級列表組件
- 7.2 新增學(xué)生
- 7.2.1 exports
- 7.2.2 自定義驗證器
- 7.2.3 異步驗證器
- 7.2.4 再識DI
- 7.2.5 屬性型指令
- 7.2.6 完成功能
- 7.2.7 小結(jié)
- 7.3 單元測試進階
- 7.4 學(xué)生列表
- 7.4.1 JSON對象與對象
- 7.4.2 單元測試
- 7.4.3 分頁模塊
- 7.4.4 子組件測試
- 7.4.5 重構(gòu)分頁
- 7.5 刪除學(xué)生
- 7.5.1 第三方dialog
- 7.5.2 批量刪除
- 7.5.3 面向?qū)ο?/a>
- 7.6 集成測試
- 7.7 編輯學(xué)生
- 7.7.1 初始化
- 7.7.2 自定義provider
- 7.7.3 更新學(xué)生
- 7.7.4 集成測試
- 7.7.5 可訂閱的路由參數(shù)
- 7.7.6 小結(jié)
- 7.8 總結(jié)
- 第八章 其它
- 8.1 打包構(gòu)建
- 8.2 發(fā)布部署
- 第九章 總結(jié)