iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0
Modern Web

Angular TDD (Test-driven development ) 從0到1系列 第 10

Angular TDD 測試從0到1: Day 10 Async vs fakeAsync 測試

  • 分享至 

  • xImage
  •  

在開發過程中,多少會遇到異步的 function,return 的結果是一個 callback function 或是Promise的物件,所以在調用 async function 時會有 時間差 的問題。

那時間差的測試案例如何撰寫呢?

先寫一個 Promise 的 timeout 來模擬時間差情境

Async 測試

  • 建立新的service -> ng g s share/data
  1. 新增一個 getDetails() 的function
  2. 用一個變數接Promise物件
  3. Promise裡面,若是成功則resolve('Data'),這邊也就是1.5秒後resolve 一個字串
  4. 最後回傳前面宣告(line 3)的Promise物件 resultPromise
export class DataService {
  getDetails() {
    const resultPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Data');
      }, 1500);
    });
    return resultPromise;
  }
}
  • 調整 UserComponent, 增加使用 DataService
    service模擬好後,接著在 component 取得 DataService 的值,如 line 21 ,使用getDetails() 若是時間到了,成功會回傳data,再用全域的data去接回傳值。
@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css'],
  providers: [UserService, DataService] // 增加DataService
})
export class UserComponent implements OnInit {
  user: {name: string};
  isLoggedIn = false;
    
  // 增加新變數
  data: string;

  constructor(private userService: UserService, 
              private dataService: DataService) { }
              //      ↑ 增加DataService
  ngOnInit() {
    this.user = this.userService.user;
    
    // 增加調用dataService, 取得 data
    this.dataService.getDetails().then((data: string) => this.data = data);
  }
}

前面已經把 Service 和 Component 都調整好,再接著處理測試案例,這次一樣寫正面和反面情境

寫 Async 測試案例(1) - 反面

  • line 3 ~ line 5 : 前一篇有提到過,還不確定的小夥伴,可以回前一天看(但簡而言之就是初始化元件,和注入service)
  • line 8 (重點): 使用 spyOn 方法,listen dataService 裡的 getDetails,function名字要和service裡面的方法同名,才能被監聽到,此外(and)會有回傳值,裡面會resolve一個字串的Promise
  • line 9 : 避免拿到undefined的情況,所以監聽狀態(detectChanges())
  • line 12 : 預期拿到的資料是 undefined , 因為還沒有用到 async 的方法,所以如果toBe('Data')會是fail的
it('shouldn\'t fetch data successfully if not called asynchronously', () => {
  // Arrange
  let fixture = TestBed.createComponent(UserComponent);
  let app = fixture.debugElement.componentInstance;
  let dataService = fixture.debugElement.injector.get(DataService);
  
  // Act 是重點!
  let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
  fixture.detectChanges();

  // Assert
  expect(app.data).toBe(undefined);
});

寫 Async 測試案例(2) - 正面

正面情境是有使用 async() 的方法,可以處理異步的 function,最後在 line 15 使用whenStable 等待結果,拿到回傳值後再處理預期結果,也就是「回傳結果要是'Data'」

                                                            // ↓加這個
it('should fetch data successfully if called asynchronously', async(() => {
  
  // Arrange
  let fixture = TestBed.createComponent(UserComponent);
  let app = fixture.debugElement.componentInstance;
  let dataService = fixture.debugElement.injector.get(DataService);
  
  // Act
  let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
  fixture.detectChanges();

  // Assert
  //      ↓等待
  fixture.whenStable().then(() => {
    expect(app.data).toBe('Data');
  });
}));

fakeAsync 測試

有沒有不是 Promise 寫法的異步 function,其實開發上也不少,所以Angular提供fakeAsync 方法,不用等待也能測試異步函式,就是用 tick() ,若要等待也可在 tick 裡寫毫秒,如此一來可以讓測試快速跑完。

                                                           // ↓ 設定假的Async
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();
   // ↓ 多拉A夢時光機來了,一秒也不想等了
   tick();
   expect(app.data).toBe('Data');
}));

本日心得

在瀏覽器的世界,Javascript 是單線程的溝通語言,而 browser 是多線程的 run time 環境,所以同步異步的函式一定會遇到,瞭解 async 和 fakeAsync 測試方法後,相信日後能撰寫更多不同的測試情境 Let us wait and see

下一篇來學習,「Isolated vs Non-Isolated」

參考資料來源:


上一篇
Angular TDD 測試從0到1: Day 9 Service 測試
下一篇
Angular TDD 測試從0到1: Day 11 Isolated vs Non-Isolated 測試
系列文
Angular TDD (Test-driven development ) 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言