iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

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

Angular TDD 測試從0到1: Day 22 HTML Template Unit Test(3) - ngIf, ngFor, ngClass

  • 分享至 

  • xImage
  •  

前一篇是好理解的 UI、CSS 測試,今天要來學習如何測試「directive」的情境。也就是「ngIf」、「ngFor」、「ngClass」,直接來看範例

今日課程

  • Testing *ngIfs
  • Testing *ngFors
  • Testing [ngClass]

測試情境

在寫測試案例時,不只正向要測,反向也要測試,才不會有落高的情境沒被測到。

ngIf

元件上,一個用參數,一個用函式當作 ngIf 的條件,當條件成立才會顯示裡面的 div

  • HTML
<ng-container *ngIf="showTitle">
    <div id="title1">First Title</div>
</ng-container>

<ng-container *ngIf="showTitleFunction()">
    <div id="title2">Second Title</div>
</ng-container>
  • Spec 寫法
    (1) 假設 showTitle = true,斷言的地方取得 id 為 title1,預期該element須存在。
    (2) 假設 showTitle = false,斷言的地方取得方式一樣,但要預期不該存在,所以用 toBeFalsy
    (3) 測試用 function 當作 ngIf 的條件,那麼一開始的時候需要用 spyOn 元件的 'showTitleFunction' 且為 true 的情境,斷言 query 的方式一樣帶入對應 id 名稱 title2,預期要存在。
    (4) 如果 function return false 的情境,預期為 toBeFalsy
  it('should show the title when showTitle is true', () => {
    //Assign
    component.showTitle = true;

    //Act
    componentFixture.detectChanges();

    //Assert
    const element = componentFixture.debugElement.query(By.css('#title1'));
    expect(element).toBeTruthy();
  });

  it('shouldnt show the title when showTitle is false', () => {
    //Assign
    component.showTitle = false;

    //Act
    componentFixture.detectChanges();

    //Assert
    const element = componentFixture.debugElement.query(By.css('#title1'));
    expect(element).toBeFalsy();
  });

  it('should show the title when showTitleFunction returns true', () => {
    //Assign
    spyOn(component,'showTitleFunction').and.returnValue(true);
    
    //Act
    componentFixture.detectChanges();
    
    //Assert
    const element = componentFixture.debugElement.query(By.css('#title2'));
    expect(element).toBeTruthy();
  });

  it('shouldnt show the title when showTitleFunction returns false', () => {
    //Assign
    spyOn(component,'showTitleFunction').and.returnValue(false);
    
    //Act
    componentFixture.detectChanges();
    
    //Assert
    const element = componentFixture.debugElement.query(By.css('#title2'));
    expect(element).toBeFalsy();
  });

ngFor

ngFor 可以用for迴圈的方式將array的值一個一個顯示出來,取決於array內的物件形式顯示對應的值。

  • HTML
<ng-container *ngFor="let message of messages">
    <div class="row">{{message}}</div>
</ng-container>
  • TS: 宣告 messages 參數
export class TestingNgForsComponent {
  title = 'example-angular-app';
  messages = ['string 1','string 2'];
}
  • Spec
    (1) 測試元素長度是否為3,一開始從元件取得 messages 參數,查詢 fixutre debugElement 有沒有名為 row 的 class,該元素長度要為3。
    (2) 測試陣列元素值是否相同,這邊要留意的是要拿 測試元件的參數 component.messages 而不是 fixture 來跑迴圈檢查值。
describe('queryAll', () => {
    it('should show the correct number of elements', () => {
      //Assign
      component.messages = ['a','b','c'];
      
      //Act
      componentFixture.detectChanges();

      //Assert
      const elements = componentFixture.debugElement.queryAll(By.css('.row'));
      expect(elements.length).toEqual(3);
    });
  
    it('should pass down the correct data to its child components', () => {
      //Assign
      component.messages = ['a','b','c'];
      
      //Act
      componentFixture.detectChanges();

      //Assert
      const elements = componentFixture.debugElement.queryAll(By.css('.row'));
      for (let i = 0; i < component.messages.length; i++) {
          expect(elements[i].nativeElement.innerText).toEqual(component.messages[i]);
      }
      
      // Do not loop over the elements
      // for (let i = 0; i < elements.length; i++) {
      //   expect(elements[i].nativeElement.innerText).toEqual(component.messages[i]);
      // }
    });
  });

ngClass

常用在不同的元件行為,要有不同的UI呈現,舉例來說:按鈕狀態可以被點選的時候 class="active",當按鈕狀態不符條件需要 disabled,class="inactive",ngClass 可以動態的根據資料狀態,檢核條件是否成立,成立才使用對應的 class 名稱。

  • HTML
<div id="title1" [ngClass]="{'active': activeTitle}">First Title</div>
<div id="title2" [ngClass]="{'active': activeTitleFunction()}">Second Title</div>
  • TS
export class TestingNgClassComponent {
  title = 'example-angular-app';

  public activeTitle = false;

  public activeTitleFunction() { return true; }
}
  • Spec (1)
    這邊其實很像 ngIf 的測試方式,所以各取參數、函式的正反向當作測試案例,實際的測試案例要,測試「參數」的正反向,和「函式」的正反向,會比較可靠。
    (1) 元素取得方式和前幾個測試案例差不多,但這邊是使用 querySelector 方式,不同的地方在 line 11toContain 判斷元素上是否有對應的 class 名稱 active
describe('querySelector', () => {
    it('should apply the class active when activeTitle is true', () => {
      //Assign
      component.activeTitle = true;
      
      //Act
      componentFixture.detectChanges();
      
      //Assert
      const element = componentFixture.debugElement.nativeElement.querySelector('#title1');
      expect(element.classList).toContain('active');
    });
        
    it('shouldnt apply the class active when activeTitleFunction returns false', () => {
      //Assign  
      spyOn(component,'activeTitleFunction').and.returnValue(false);
        
      //Act
      componentFixture.detectChanges();
        
      //Assert
      const element = componentFixture.debugElement.nativeElement.querySelector('#title2');
      expect(element.classList).not.toContain('active');
    });
  })
  • Spec (2) 用 querySelectorAll 取元素
    (1) 那麼這邊的差異是 line 11取得元素後的第一個 index 是否有對應的 class 名稱。
describe('querySelectorAll', () => {
    it('should apply the class active when activeTitle is true', () => {
      //Assign
      component.activeTitle = true;
      
      //Act
      componentFixture.detectChanges();
      
      //Assert
      const elements = componentFixture.debugElement.nativeElement.querySelectorAll('#title1');
      expect(elements[0].classList).toContain('active');
    });    
})

今日心得

明天邁向 HTML 測試最後一篇,雖然今天的範例很簡單很好理解,如果 DOM 元素操作很熟悉的讀者相信很快就能吸收,如果新手讀者的話,希望這邊可以當作範例工具文章,理解常見的 directive 測試方式。


上一篇
Angular TDD 測試從0到1: Day 21 HTML Template Unit Test(2) - Change Detector and Display
下一篇
Angular TDD 測試從0到1: Day 23 HTML Template Unit Test(4) - @Input, @Output
系列文
Angular TDD (Test-driven development ) 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言