debugElement.query vs nativeElement.querySelector

在 Angular Jasmine 測試中,有兩種主要的 DOM 查詢方法,各有不同的使用時機和優勢。

debugElement.query

語法

const element = fixture.debugElement.query(By.css('selector'));
const elements = fixture.debugElement.queryAll(By.css('selector'));

優點

  • 支援 Angular 特定選擇器 - By.directive(Component)

  • 跨平台兼容 - 適用於非瀏覽器環境

  • Angular 開發者友善 - 提供 Angular 特定功能

適用時機

查找 Angular 元件

// ✅ 推薦:查找 Angular 元件
const toggleComponent = fixture.debugElement.query(By.css('app-slide-toggle'));
const matIcon = fixture.debugElement.query(By.css('mat-icon'));

使用 Angular 特定選擇器

// ✅ 推薦:使用指令選擇器
const directive = fixture.debugElement.query(By.directive(MyDirective));

// ✅ 推薦:查找有特定指令的元素
const tooltipElement = fixture.debugElement.query(By.directive(MatTooltip));

需要跨平台支援

// ✅ 推薦:當測試需要在非瀏覽器環境執行時
const component = fixture.debugElement.query(By.css('app-component'));

nativeElement.querySelector

語法

const element = fixture.nativeElement.querySelector('selector');
const elements = fixture.nativeElement.querySelectorAll('selector');

優點

  • 原生 DOM API - 熟悉的瀏覽器 API

  • 性能較好 - 直接操作 DOM

  • 支援複雜選擇器 - CSS 選擇器功能完整

適用時機

查找普通 HTML 元素

// ✅ 推薦:查找普通 HTML 元素
const button = fixture.nativeElement.querySelector('button');
const div = fixture.nativeElement.querySelector('div');
const span = fixture.nativeElement.querySelector('span');

使用 CSS class 或 ID

// ✅ 推薦:使用 CSS class
const warningIcon = fixture.nativeElement.querySelector('.warning-icon');
const errorMessage = fixture.nativeElement.querySelector('.error-message');

// ✅ 推薦:使用 ID
const header = fixture.nativeElement.querySelector('#main-header');

需要複雜選擇器

// ✅ 推薦:複雜的 CSS 選擇器
const element = fixture.nativeElement.querySelector('div.class:nth-child(2)');
const tableCell = fixture.nativeElement.querySelector('table tbody tr:first-child td');

// ✅ 推薦:多重選擇器
const elements = fixture.nativeElement.querySelectorAll('thead th');

實務範例

測試條件渲染

it('should render group by header for non-CSV data source', () => {
  fixture.componentRef.setInput('isDataFromCsv', false);
  fixture.detectChanges();

  // Angular 元件 - 用 debugElement.query
  const groupByToggle = fixture.debugElement.query(By.css('app-slide-toggle'));
  expect(groupByToggle).not.toBeNull();
});

it('should display warning icon when needed', () => {
  // 設定顯示警告的條件
  component.groupByState.set({ isEnabled: true, columnsId: [], rule: {} });
  fixture.detectChanges();

  // CSS class - 用 nativeElement.querySelector  
  const warningIcon = fixture.nativeElement.querySelector('.warning-icon');
  expect(warningIcon).not.toBeNull();
});

測試表格結構

it('should render all data columns as table rows', () => {
  const testData = [
    { id: '1', columnName: 'Column 1', type: 'X', genericDataType: 'Numeric' },
    { id: '2', columnName: 'Column 2', type: 'Y', genericDataType: 'Varchar' }
  ];
  
  fixture.componentRef.setInput('dataColumns', testData);
  fixture.detectChanges();

  // 查找表格列 - 用 nativeElement.querySelectorAll
  const tableRows = fixture.nativeElement.querySelectorAll('tbody tr');
  expect(tableRows.length).toBe(2);
});

測試元件互動

it('should emit event when icon button is clicked', () => {
  spyOn(component.removeColumn, 'emit');
  
  // 查找 Angular 元件 - 用 debugElement.query
  const iconBtn = fixture.debugElement.query(By.css('app-icon-btn'));
  
  // 觸發事件
  iconBtn.triggerEventHandler('btnClickOutput', 'test-id');
  
  expect(component.removeColumn.emit).toHaveBeenCalledWith('test-id');
});

選擇指南

使用 debugElement.query 當:

  • 查找 Angular 元件 (app-*, mat-*)

  • 需要使用 By.directive() 選擇器

  • 測試需要 跨平台支援

使用 nativeElement.querySelector 當:

  • 查找 普通 HTML 元素 (div, span, button)

  • 使用 CSS class 或 ID (.class, #id)

  • 需要 複雜的 CSS 選擇器

  • 重視 查詢性能

記憶口訣

「Angular 的用 debugElement,HTML 的用 nativeElement」

這樣的分工讓測試程式碼更清晰,也更容易維護!

Last updated