測試中的表單元素操作
在 Angular 應用的測試中,處理表單元素是一個常見的需求。我們需要模擬使用者的輸入操作、測試表單驗證邏輯、檢查表單控制狀態等。本篇筆記將詳細介紹如何在測試中操作 Angular 的表單元素。
基本表單輸入模擬
在測試中,我們經常需要模擬使用者在表單中的輸入。以下是在 Angular 測試中操作基本表單元素的方法。
為 input 元素賦值
it('應該更新輸入框的值', () => {
// 獲取輸入元素
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
// 設置值
inputElement.value = '測試輸入';
// 觸發 input 事件,模擬使用者輸入
inputElement.dispatchEvent(new Event('input'));
// 若使用 ngModel 或 Reactive Forms 還需要觸發變更檢測
fixture.detectChanges();
// 斷言:檢查元件屬性是否更新
expect(component.inputValue).toBe('測試輸入');
});
使用 setValue 直接修改表單控制值
如果使用 Reactive Forms,可以直接設置 FormControl 的值:
it('應該通過 FormControl 設置值', () => {
// 設置 FormControl 的值
component.form.get('username').setValue('test_user');
fixture.detectChanges();
// 檢查 DOM 是否反映了變更
const inputElement = fixture.debugElement.query(By.css('input[formControlName="username"]')).nativeElement;
expect(inputElement.value).toBe('test_user');
});
處理不同類型的表單控制項
不同類型的表單控制項需要不同的處理方式。
文本輸入框 (text, email, password 等)
it('應該更新電子郵件輸入', () => {
const emailInput = fixture.debugElement.query(By.css('input[type="email"]')).nativeElement;
emailInput.value = 'test@example.com';
emailInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(component.email).toBe('test@example.com');
});
複選框 (checkbox)
it('應該切換複選框狀態', () => {
const checkbox = fixture.debugElement.query(By.css('input[type="checkbox"]')).nativeElement;
// 初始狀態
expect(checkbox.checked).toBe(false);
// 點擊複選框
checkbox.click();
// 或者直接設置 checked 屬性
// checkbox.checked = true;
// 觸發 change 事件
checkbox.dispatchEvent(new Event('change'));
fixture.detectChanges();
// 檢查元件狀態
expect(component.isAgreed).toBe(true);
// 檢查 DOM 狀態
expect(checkbox.checked).toBe(true);
});
單選按鈕 (radio)
it('應該選擇正確的單選按鈕', () => {
// 獲取所有單選按鈕
const radioButtons = fixture.debugElement.queryAll(By.css('input[type="radio"]'));
// 選擇第二個選項
const secondRadio = radioButtons[1].nativeElement;
secondRadio.checked = true;
secondRadio.dispatchEvent(new Event('change'));
fixture.detectChanges();
// 檢查是否選中
expect(secondRadio.checked).toBe(true);
expect(component.selectedOption).toBe(secondRadio.value);
});
下拉選單 (select)
it('應該選擇下拉選單選項', () => {
const select = fixture.debugElement.query(By.css('select')).nativeElement;
// 選擇第二個選項
select.value = select.options[1].value;
select.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(component.selectedValue).toBe(select.value);
});
文本區域 (textarea)
it('應該更新文本區域', () => {
const textarea = fixture.debugElement.query(By.css('textarea')).nativeElement;
textarea.value = '這是一段測試文本';
textarea.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(component.comments).toBe('這是一段測試文本');
});
測試 Angular 表單驗證
測試表單驗證是 Angular 測試的重要部分。
測試必填驗證
it('當必填字段為空時應顯示錯誤', () => {
const form = component.form;
const nameControl = form.get('name');
// 設置為空並標記為 touched
nameControl.setValue('');
nameControl.markAsTouched();
fixture.detectChanges();
// 檢查驗證狀態
expect(nameControl.valid).toBe(false);
expect(nameControl.errors.required).toBeTruthy();
// 檢查 DOM 中是否顯示錯誤訊息
const errorElement = fixture.debugElement.query(By.css('.name-error'));
expect(errorElement).toBeTruthy();
expect(errorElement.nativeElement.textContent).toContain('此欄位必填');
});
測試自定義驗證器
it('應驗證密碼複雜度', () => {
const form = component.form;
const passwordControl = form.get('password');
// 設置簡單密碼
passwordControl.setValue('12345');
fixture.detectChanges();
// 檢查驗證狀態
expect(passwordControl.valid).toBe(false);
expect(passwordControl.errors.minlength).toBeTruthy();
// 設置符合要求的密碼
passwordControl.setValue('Complex123!');
fixture.detectChanges();
expect(passwordControl.valid).toBe(true);
expect(passwordControl.errors).toBeNull();
});
表單事件觸發
除了基本的值設置外,有時我們還需要測試表單事件。
提交表單
it('應處理表單提交', () => {
// 設置表單值
component.form.get('username').setValue('testuser');
component.form.get('password').setValue('password123');
// 監聽 submit 方法
spyOn(component, 'onSubmit').and.callThrough();
// 觸發表單提交
const form = fixture.debugElement.query(By.css('form'));
form.triggerEventHandler('submit', {});
// 檢查 submit 方法是否被調用
expect(component.onSubmit).toHaveBeenCalled();
});
測試按鍵事件
it('按下 Enter 鍵應提交表單', () => {
// 監聽提交方法
spyOn(component, 'onSubmit');
// 獲取輸入元素
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
// 模擬按鍵事件
const enterEvent = new KeyboardEvent('keyup', {
key: 'Enter',
bubbles: true
});
inputElement.dispatchEvent(enterEvent);
// 檢查是否調用了提交方法
expect(component.onSubmit).toHaveBeenCalled();
});
測試 Angular Material 表單元件
Angular Material 元件通常需要特殊的測試方法。
Material Input
it('應該設置 Material 輸入框的值', () => {
// 獲取 MatInput 元素
const matInput = fixture.debugElement.query(By.css('input.mat-input-element')).nativeElement;
// 設置值
matInput.value = '測試值';
matInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(component.inputValue).toBe('測試值');
});
Material Select
it('應該選擇 Material Select 選項', async () => {
// 打開選擇器
const matSelect = fixture.debugElement.query(By.css('mat-select')).nativeElement;
matSelect.click();
fixture.detectChanges();
// 等待選項面板渲染
await fixture.whenStable();
// 選擇選項
const options = document.querySelectorAll('mat-option');
options[1].click(); // 選擇第二個選項
fixture.detectChanges();
// 檢查選擇結果
expect(component.selectedValue).toBe('option2');
});
Material Checkbox
it('應該切換 Material Checkbox', () => {
const matCheckbox = fixture.debugElement.query(By.css('mat-checkbox')).nativeElement;
matCheckbox.click();
fixture.detectChanges();
expect(component.isChecked).toBe(true);
});
Material Radio Button
it('應該選擇 Material Radio 按鈕', () => {
const radioButtons = fixture.debugElement.queryAll(By.css('mat-radio-button'));
radioButtons[1].nativeElement.click();
fixture.detectChanges();
expect(component.selectedOption).toBe('option2');
});
Angular 17+ 的 Input Signal 值設置
從 Angular 17 開始,引入了新的 input()
函數語法來定義輸入屬性。在測試中設置這些 input 值需要特殊的方法。
使用 setInput 方法
當測試 Angular 17+ 具有 input() 函數定義的元件時,可以使用 setInput
方法:
it('應該設置 title 輸入', () => {
// 使用 setInput 方法設置 input 值
fixture.componentRef.setInput('title', '測試標題');
fixture.detectChanges();
// 注意:input() 返回的是一個函數,需要調用它獲取值
expect(component.title()).toBe('測試標題');
// 檢查 DOM 是否反映了變更
const titleElement = fixture.nativeElement.querySelector('.title');
expect(titleElement.textContent).toContain('測試標題');
});
在元件創建時提供輸入值
也可以在創建元件時直接提供初始的輸入值:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [MyComponent]
});
// 創建元件時提供輸入值
fixture = TestBed.createComponent(MyComponent, {
componentInputs: {
title: '預設標題',
options: [{ id: 1, name: '選項1' }],
disabled: false
}
});
component = fixture.componentInstance;
fixture.detectChanges();
});
it('應該有正確的初始輸入值', () => {
expect(component.title()).toBe('預設標題');
expect(component.options().length).toBe(1);
expect(component.disabled()).toBe(false);
});
測試預設值
測試當沒有提供輸入值時組件使用的預設值:
it('title 沒有提供時應使用預設值', () => {
// 創建元件時不提供 title 輸入
const defaultFixture = TestBed.createComponent(MyComponent);
const defaultComponent = defaultFixture.componentInstance;
defaultFixture.detectChanges();
// 檢查是否使用了預設值
expect(defaultComponent.title()).toBe(''); // 假設預設值是空字串
});
動態更改輸入值
測試更新輸入值時組件的響應:
it('更新輸入值後應反映變更', () => {
// 初始設置
fixture.componentRef.setInput('count', 5);
fixture.detectChanges();
expect(component.count()).toBe(5);
// 更新值
fixture.componentRef.setInput('count', 10);
fixture.detectChanges();
// 檢查更新後的值
expect(component.count()).toBe(10);
// 檢查 DOM 是否反映了變更
const countElement = fixture.nativeElement.querySelector('.count-display');
expect(countElement.textContent).toContain('10');
});
Last updated