Angular 測試實用案例大全
這篇筆記收集了 Angular 測試中的各種實用案例,涵蓋 DOM 元素檢查、表單操作、非同步處理等各個方面。每個案例都提供了實際可用的測試代碼,可作為日常開發中的參考和複製使用。
DOM 元素檢查案例
檢查元素是否存在
it('應顯示標題元素', () => {
fixture.detectChanges();
const titleElement = fixture.debugElement.query(By.css('.title'));
expect(titleElement).toBeTruthy();
});
檢查元素不存在
it('錯誤訊息應該不顯示', () => {
fixture.detectChanges();
const errorElement = fixture.debugElement.query(By.css('.error-message'));
expect(errorElement).toBeNull();
});
檢查文字內容
it('應顯示正確的標題文字', () => {
component.title = '測試標題';
fixture.detectChanges();
const titleElement = fixture.debugElement.query(By.css('h1')).nativeElement;
expect(titleElement.textContent).toContain('測試標題');
});
檢查特定 CSS 類別
it('應該有正確的 CSS 類別', () => {
component.isActive = true;
fixture.detectChanges();
const element = fixture.debugElement.query(By.css('.container')).nativeElement;
expect(element.classList).toContain('active');
});
檢查元素的屬性
it('圖片應該有正確的來源', () => {
fixture.detectChanges();
const imgElement = fixture.debugElement.query(By.css('img')).nativeElement;
expect(imgElement.src).toContain('test-image.jpg');
expect(imgElement.alt).toBe('測試圖片');
});
檢查 CSS 偽元素內容
it('必填欄位應顯示星號', () => {
fixture.detectChanges();
const requiredLabel = fixture.debugElement.query(By.css('.required-field')).nativeElement;
const afterContent = window.getComputedStyle(requiredLabel, ':after').getPropertyValue('content');
expect(afterContent).toBe('"*"');
});
測試條件渲染
it('當設置顯示標誌時應顯示特定元素', () => {
// 初始狀態
component.showDetails = false;
fixture.detectChanges();
let detailsElement = fixture.debugElement.query(By.css('.details'));
expect(detailsElement).toBeNull();
// 改變狀態
component.showDetails = true;
fixture.detectChanges();
// 檢查元素現在是否存在
detailsElement = fixture.debugElement.query(By.css('.details'));
expect(detailsElement).toBeTruthy();
});
表單元素操作案例
設置輸入框的值
it('應正確更新輸入框的值', () => {
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
// 設置值
inputElement.value = '測試輸入';
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
// 檢查元件屬性
expect(component.inputValue).toBe('測試輸入');
});
操作 Reactive Forms
it('應透過 FormControl 設置值並更新 DOM', () => {
// 設置 FormControl 的值
component.form.get('username').setValue('testUser');
fixture.detectChanges();
// 檢查 DOM 是否更新
const inputElement = fixture.debugElement.query(By.css('input[formControlName="username"]')).nativeElement;
expect(inputElement.value).toBe('testUser');
});
操作複選框
it('應正確切換複選框狀態', () => {
const checkbox = fixture.debugElement.query(By.css('input[type="checkbox"]')).nativeElement;
// 點擊複選框
checkbox.click();
fixture.detectChanges();
// 檢查元件狀態
expect(component.isChecked).toBe(true);
// 檢查 DOM 狀態
expect(checkbox.checked).toBe(true);
});
操作單選按鈕
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(component.selectedOption).toBe(secondRadio.value);
});
操作下拉選單
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);
});
測試表單驗證
it('應正確驗證必填欄位', () => {
const nameControl = component.form.get('name');
// 設置為空並標記為 touched
nameControl.setValue('');
nameControl.markAsTouched();
fixture.detectChanges();
// 檢查驗證狀態
expect(nameControl.valid).toBe(false);
expect(nameControl.errors.required).toBeTruthy();
// 檢查錯誤訊息
const errorElement = fixture.debugElement.query(By.css('.name-error'));
expect(errorElement.nativeElement.textContent).toContain('此欄位必填');
});
測試表單提交
it('應處理表單提交', () => {
// 設置表單值
component.form.setValue({
username: 'testuser',
password: 'password123'
});
// 監聽 submit 方法
spyOn(component, 'onSubmit').and.callThrough();
// 觸發表單提交
const form = fixture.debugElement.query(By.css('form'));
form.triggerEventHandler('submit', {});
// 檢查是否調用了 submit 方法
expect(component.onSubmit).toHaveBeenCalled();
});
Angular 17+ 的 Input Signal 設置
it('應設置元件的 Input Signal', () => {
// 使用 setInput 方法設置 input 值
fixture.componentRef.setInput('title', '測試標題');
fixture.detectChanges();
// 檢查 input signal 值
expect(component.title()).toBe('測試標題');
// 檢查 DOM 是否更新
const titleElement = fixture.nativeElement.querySelector('.title');
expect(titleElement.textContent).toContain('測試標題');
});
測試 Angular 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');
});
測試錯誤訊息顯示
it('應根據需要顯示錯誤訊息', () => {
// 初始檢查
let errorElement = fixture.debugElement.query(By.css('.error-message'));
expect(errorElement).toBeNull();
// 設置錯誤訊息
fixture.componentRef.setInput('errorMessage', '請選擇至少一個選項');
fixture.detectChanges();
// 檢查錯誤訊息是否顯示
errorElement = fixture.debugElement.query(By.css('.error-message'));
expect(errorElement).toBeTruthy();
expect(errorElement.nativeElement.textContent).toContain('請選擇至少一個選項');
});
非同步操作案例
等待元素載入完成
it('應在元素載入後顯示數據', async () => {
// 觸發數據載入
component.loadData();
fixture.detectChanges();
// 等待載入完成
await fixture.whenStable();
// 檢查數據是否顯示
const dataElements = fixture.debugElement.queryAll(By.css('.data-item'));
expect(dataElements.length).toBeGreaterThan(0);
});
測試 API 調用
it('應呼叫API並處理回應', async () => {
// 模擬服務響應
const testData = [{ id: 1, name: '測試項目' }];
spyOn(dataService, 'getData').and.returnValue(of(testData));
// 觸發數據載入
component.loadData();
fixture.detectChanges();
// 檢查服務是否被調用
expect(dataService.getData).toHaveBeenCalled();
// 檢查元件數據是否更新
expect(component.items).toEqual(testData);
// 檢查 DOM 是否更新
const itemElements = fixture.debugElement.queryAll(By.css('.item'));
expect(itemElements.length).toBe(1);
expect(itemElements[0].nativeElement.textContent).toContain('測試項目');
});
測試動態下拉選單
it('點擊 mat-select 後應顯示選項', async () => {
// 初始檢查
let optionsPanel = document.querySelector('.mat-select-panel');
expect(optionsPanel).toBeNull();
// 點擊 select 以展開選項
const select = fixture.debugElement.query(By.css('mat-select')).nativeElement;
select.click();
fixture.detectChanges();
// 等待面板渲染
await fixture.whenStable();
// 檢查選項面板是否存在
optionsPanel = document.querySelector('.mat-select-panel');
expect(optionsPanel).toBeTruthy();
// 檢查選項數量
const options = document.querySelectorAll('.mat-option');
expect(options.length).toBe(3); // 假設有 3 個選項
});
測試動態加載的搜尋框
it('searchable 設置為 true 應顯示搜尋框', async () => {
// 設置輸入參數
fixture.componentRef.setInput('searchable', true);
fixture.detectChanges();
// 點擊 select 以顯示下拉選單
const selectElement = fixture.elementRef.nativeElement.querySelector('mat-select');
selectElement.click();
fixture.detectChanges();
// 等待面板渲染
await fixture.whenStable();
// 檢查搜尋框是否存在
const searchElement = document.querySelector('.mat-select-search-input');
expect(component.searchable()).toBe(true);
expect(searchElement).toBeTruthy();
});
使用 fakeAsync 和 tick
it('應在延遲後顯示訊息', fakeAsync(() => {
component.showMessageAfterDelay();
// 推進時間
tick(1000);
fixture.detectChanges();
// 檢查訊息是否顯示
const message = fixture.nativeElement.querySelector('.message');
expect(message.textContent).toContain('延遲訊息');
}));
事件處理案例
測試點擊事件
it('點擊按鈕應觸發事件處理', () => {
// 監聽事件處理方法
spyOn(component, 'handleClick');
// 點擊按鈕
const button = fixture.debugElement.query(By.css('button'));
button.nativeElement.click();
// 檢查事件處理方法是否被調用
expect(component.handleClick).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();
});
測試自訂事件
it('自訂事件應正確觸發', () => {
// 創建事件監聽 spy
const eventSpy = spyOn(component.itemSelected, 'emit');
// 觸發事件
component.selectItem(123);
// 檢查事件是否被觸發並傳遞正確的值
expect(eventSpy).toHaveBeenCalledWith(123);
});
測試拖放事件
it('應處理拖放事件', () => {
// 監聽拖放處理方法
spyOn(component, 'onDrop').and.callThrough();
// 創建拖放事件
const dropEvent = {
preventDefault: jasmine.createSpy('preventDefault'),
dataTransfer: {
getData: () => '123'
}
};
// 獲取拖放區域
const dropZone = fixture.debugElement.query(By.css('.drop-zone'));
// 觸發拖放事件
dropZone.triggerEventHandler('drop', dropEvent);
// 檢查處理方法是否被調用
expect(component.onDrop).toHaveBeenCalled();
expect(dropEvent.preventDefault).toHaveBeenCalled();
// 檢查拖放項目是否正確處理
expect(component.droppedItemId).toBe('123');
});
複雜用例案例
完整測試登入表單
describe('登入表單', () => {
it('應驗證表單並提交', () => {
// 獲取表單控制項
const emailInput = fixture.debugElement.query(By.css('input[formControlName="email"]')).nativeElement;
const passwordInput = fixture.debugElement.query(By.css('input[formControlName="password"]')).nativeElement;
// 輸入無效的電子郵件
emailInput.value = 'invalid-email';
emailInput.dispatchEvent(new Event('input'));
// 輸入有效的密碼
passwordInput.value = 'valid-password';
passwordInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
// 檢查表單驗證
expect(component.loginForm.get('email').valid).toBe(false);
expect(component.loginForm.valid).toBe(false);
// 修正電子郵件
emailInput.value = 'valid@example.com';
emailInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
// 現在表單應該有效
expect(component.loginForm.valid).toBe(true);
// 監聽登入方法
spyOn(component, 'login');
// 提交表單
const form = fixture.debugElement.query(By.css('form'));
form.triggerEventHandler('submit', {});
// 檢查是否調用了登入方法
expect(component.login).toHaveBeenCalled();
});
});
測試動態表單控制項
describe('動態表單控制項', () => {
it('應動態添加表單控制項', () => {
// 初始檢查
expect(component.dynamicForm.get('items').length).toBe(1);
// 點擊「添加項目」按鈕
const addButton = fixture.debugElement.query(By.css('.add-item-btn'));
addButton.nativeElement.click();
fixture.detectChanges();
// 檢查是否添加了新的控制項
expect(component.dynamicForm.get('items').length).toBe(2);
// 設置第二個控制項的值
const formArray = component.dynamicForm.get('items') as FormArray;
formArray.at(1).get('name').setValue('第二項');
fixture.detectChanges();
// 檢查 DOM 是否反映了變更
const inputElements = fixture.debugElement.queryAll(By.css('input[formControlName="name"]'));
expect(inputElements.length).toBe(2);
expect(inputElements[1].nativeElement.value).toBe('第二項');
});
});
測試 Material Search 元件
describe('Material 搜尋元件', () => {
it('應在輸入後過濾選項', async () => {
// 打開選擇器
const matSelect = fixture.debugElement.query(By.css('mat-select')).nativeElement;
matSelect.click();
fixture.detectChanges();
await fixture.whenStable();
// 查找搜尋輸入框
const searchInput = document.querySelector('input.mat-select-search-input');
expect(searchInput).toBeTruthy();
// 輸入搜尋文字
searchInput.value = '選項1';
searchInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
// 檢查過濾後的選項
const visibleOptions = document.querySelectorAll('mat-option:not(.mat-select-search-no-entries)');
expect(visibleOptions.length).toBe(1);
expect(visibleOptions[0].textContent).toContain('選項1');
});
});
測試表單錯誤訊息和樣式
describe('表單錯誤訊息', () => {
it('應顯示錯誤訊息並應用適當樣式', () => {
// 初始檢查
let errorElement = fixture.debugElement.query(By.css('.error-message'));
expect(errorElement).toBeNull();
// 設置錯誤訊息
fixture.componentRef.setInput('errorMessage', '請選擇至少一個選項');
fixture.detectChanges();
// 檢查錯誤訊息和樣式
errorElement = fixture.debugElement.query(By.css('.error-message'));
expect(errorElement).toBeTruthy();
expect(errorElement.nativeElement.textContent).toContain('請選擇至少一個選項');
const selectElement = fixture.debugElement.query(By.css('.select-container')).nativeElement;
expect(selectElement.classList).toContain('has-error');
});
});
測試自動完成功能
describe('自動完成輸入框', () => {
it('應顯示和篩選自動完成選項', async () => {
// 獲取自動完成輸入框
const autocompleteInput = fixture.debugElement.query(
By.css('input[formControlName="city"]')
).nativeElement;
// 聚焦並開始輸入
autocompleteInput.focus();
autocompleteInput.value = '台';
autocompleteInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
// 等待自動完成選項出現
await fixture.whenStable();
// 檢查自動完成面板是否顯示
const matOptions = document.querySelectorAll('mat-option');
expect(matOptions.length).toBeGreaterThan(0);
// 驗證選項已經過濾
expect(Array.from(matOptions).some(option =>
option.textContent.includes('台北')
)).toBeTrue();
// 選擇一個選項
matOptions[0].click();
fixture.detectChanges();
// 檢查選擇的值是否正確設置
expect(component.form.get('city').value).toBe(matOptions[0].getAttribute('ng-reflect-value'));
});
});
結語
這些案例涵蓋了 Angular 測試中常見的各種情境,可以根據需要複製和調整來測試你的應用程式。
Last updated