【Angular】Jestで孫コンポーネントにアクセス

# はじめに

[Angular](https://angular.io/)のUIコンポーネントの一つである[primeNg](https://primeng.org/)を[Jest](https://jestjs.io/ja/)でユニットテストをするのに、[Angular Material](https://material.angular.io/)にある[Harnesses](https://material.angular.io/cdk/test-harnesses/overview)が欲しくなりました。
しかしprimeNgにはHarnessesにあたるものが存在しなくて、代替案を見つけたので共有します。

# TL;DR

```ts
const debugEl = fixture.debugElement.query(By.directive(GrandchildComponent));
const grandchildComponentInstance: GrandchildComponent = debugEl.componentInstance;
GrandchildComponent.method();
```

# 対象コンポーネント

これをJestでテストします。
「はじめに」に記載の通り、primeNgを用います。
また、standaloneコンポーネントで行います。

## HTML

```html
<div>
  <div>first = {{ first }}</div>
  <div>rows = {{ rows }}</div>
  <p-paginator
    (onPageChange)="onPageChange($event)"
    [first]="first"
    [rows]="rows"
    [totalRecords]="120"
  ></p-paginator>
</div>
```

## TypeScript

```ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PaginatorModule, PaginatorState } from 'primeng/paginator';

@Component({
  selector: 'app-target',
  standalone: true,
  imports: [CommonModule, PaginatorModule],
  templateUrl: './target.component.html',
  styleUrls: ['./target.component.scss']
})
export class AppTargetComponent {
  first = 0;
  rows = 10;

  onPageChange(event: PaginatorState) {
    this.first = event.first ?? this.first;
  }
}
```

# Jest

上記コンポーネントでページ遷移したときに走る `onPageChange` がemitされたときの挙動をテストします。
`test paginator` で確認します。

```ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Paginator } from 'primeng/paginator';

import { AppTargetComponent } from './target.component';

describe('AppTargetComponent', () => {
  let component: AppTargetComponent;
  let fixture: ComponentFixture<AppTargetComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [AppTargetComponent],
    });
    fixture = TestBed.createComponent(AppTargetComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('test paginator', () => {
    const debugEl = fixture.debugElement.query(By.directive(Paginator));
    const paginatorComponentInstance: Paginator = debugEl.componentInstance;
    paginatorComponentInstance.onPageChange.emit({
      page: 3,
      first: 30,
      rows: 10,
    });
    expect(component.first).toEqual(30);
  });
});
```

まずは

```ts
fixture.debugElement.query(By.directive(Paginator));
```

で対象コンポーネントのDOMから `p-paginator` を指定します。
`p-paginator` は `Paginator` と [ドキュメント](https://primeng.org/paginator) にありますので、その通り指定します。
ただこのままだと `DebugElement` ですので、そのコンポーネントインスタンスを `debugEl.componentInstance` で取得します。
これで[AngularのViewChild](https://angular.jp/api/core/ViewChild)に似た挙動を行えます。

あとはそのコンポーネントインスタンスから呼び出したいメソッド (ここではページネーションしたときの挙動をテストしたいので[OnPageChange](https://primeng.org/paginator#api.paginator.emitters)のemitされるメソッド)を呼び出します。

```ts
onPageChange(event: PaginatorState) {
  this.first = event.first ?? this.first;
}
```

でfirstに値を更新していますので、 `expect(component.first).toEqual(30)` で必要な値が入っているかを確認して完了です。

# おわりに

簡単ではありますが、調べても見つからなかったので書きました。
終わります。