2024/12/21 Angular 日々のテストテクニック、2
Angular の effect 、いつ使いますか? わたしは可能なら必要になるまで使いたくないです Effects are rarely needed in most application code, but may be useful in specific circumstances.
公式ドキュメントにもあるように、本当に effect を使う場面なのかというのは気にしたほうが良さそうです
ReactのuseEffect にもこういったドキュメントもありますね 概念として類似したしたものとして自分は捉えていますが、基本的には {Angular, React} が関知しない外側で実施したい事項がある場合に限るような理解です
サードパーティのウィジェットをDOMへアタッチする
外部システムに情報を送信する
IntersectionObserver のようなユーザ操作を検知するリスナーの取り付け取り外しをする
場面はこう仮定しましょう
UIコンポーネント + フォーム入力項目、を別のコンポーネントに分けます
MyFormComponent + MyFormState で、後者は各入力項目を受け持つようにし、前回入力されたemailを localStorage から復元すると仮定します
code:code.ts
// UI コンポーネントでは
@Component({/*...*/})
class MyFormComponent {
readonly form = inject(MyFormState);
}
// フォーム管理では
class MyFormState {
readonly email = signal('')
constructor() {
effect(() => {/* localStorage から復元 this.email.set(restoredEmail) */});
}
}
テストを書く際に、安直には const form = new MyFormState() と初期化して form.email.set('foo@example') のようなセットアップをテスト前に実行したいと考えます
(※ そういったテストケースを書く必要があるかはさておきます)
code:spec.ts
const form = new MyFormState();
form.email.set('foo@bar.exmaple');
const { detectChanges } = await render(MyFormComponent, {
});
detectChanges();
しかしこの場合以下のようなエラーメッセージが表示されます
Error: NG0203: effect() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext. Find more at https://angular.dev/errors/NG020 要約すると初期化されてから Injection Context が決定されているため出ているエラーです
UIコンポーネントでinjectされる前に初期化されていることが問題のようですね
code:spec.ts
const { detectChanges } = await render(MyFormComponent, {
providers: [
{ provide: MyFormState, useFactory: () => new MyFormState() },
],
});
const form = TestBed.inject(MyFormState);
form.email.set('foo@bar.exmaple');
detectChanges();
コンポーネントの初期化に合わせて依存を注入し、TestBed.inject(MyFormState) で依存オブジェクトを取得します
effect に限らず依存がどこで注入されるかがテストでは重要ではありますが、effect を使った場合につまづきやすいポイントだなと感じました
今日は以上です