Component Harness作成の使い分け
TestbedHarnessEnvironment.loader
Gets a HarnessLoader instance for the given fixture, rooted at the fixture's root element. Should be used to create harnesses for elements contained inside the fixture
- Fixture内に存在する子コンポーネントのHarnessを作成するときに使う。
TestbedHarnessEnvironment.documentRootLoader
Gets a HarnessLoader instance for the given fixture, rooted at the HTML document's root element. Can be used to create harnesses for elements that fall outside of the fixture
- Fixture外(すなわちドキュメントルート)に存在するコンポーネントのHarnessを作成するときに使う。例えばダイアログ等。
TestbedHarnessEnvironment.harnessForFixture
Used to create a ComponentHarness instance for the fixture's root element directly.
This is necessary when bootstrapping the test with the component you plan to load a harness for, because Angular does not set the proper tag name when creating the fixture.
- Fixture自身のHarnessを作成するときに使う。つまりTestbedHarnessEnvironment.loaderではFixture自身のHarnessを作成できない。
TestbedHarnessEnvironment.loaderではFixture自身のHarnessを作成できない
- なぜか?
- Fixtureの作成時にAngularが適切なタグ名を設定しないから。
- そもそもFixtureとは?
- 適切な「タグ」とは?
- なぜ「適切なタグ」が無いといけない?
- Fixtureの作成時にAngularが適切なタグ名を設定しないから。
そもそもFixtureとは?
- Fixture: 備品、設備、固定されたもの
-
ComponentFixture
-
Fixture for debugging and testing a component.
- Propertiesを見るに、対象コンポーネントの要素およびクラスのインスタンスを保持している。
-
-
Testing Components Basics - ComponentFixture
-
The ComponentFixture is a test harness for interacting with the created component and its corresponding element.
- ComponentFixutre自身もHarnessのようだ。
-
-
ComponentFixture
- 上記の説明からComponentFixuture自身もHarnessの役目を持ち、テスト上で要素やComponent Classを操作する際にこのFixtureが提供するAPIを使用して操作する。
適切な「タグ」とは?
-
ngc
してみてもいまいち分からんかったので後回し - (調査した結果…)そのまま。
<body>
とか<div>
みたいなやつをタグと言う。- Angularのコンポーネントだと
<app-header>
みたいな感じ。
- Angularのコンポーネントだと
なぜ「適切なタグ」が無いといけない?
- Fixtureで保持しているnativeElementからHarnessで指定された
hostSelector
を使って要素を取得しにいく。- その取得した要素からHarnessインスタンスを作成する。
- が、FixtureつまりそのContainer Component自体には自身のタグは存在しない。(
.component.ts
のselector: 'app-hoge'
で宣言されている。)- そのためnativeElementから取得できず、最後の判定処理でundefinedになるため、例外が投げられる。
- だから「適切なタグ」が無いためHarnessインスタンスを作ることができない。
- が、FixtureつまりそのContainer Component自体には自身のタグは存在しない。(
- その取得した要素からHarnessインスタンスを作成する。
TestbedHarnessEnvironmentを読んで見る
- static loader()
- 引数として受け取ったfixtureの
nativeElement
を使ってTestbedHarnessEnvironmentインスタンスを生成している。 -
constructor()
で親クラスのconstructor()
に受け取ったnativeElement
を渡している。- 親クラス
HArnessEnvironment
は抽象クラス-
createTestElement()
に子クラスから受け取ったnativeElement
を使ってTestElement
オブジェクトを生成する。このメソッドは抽象メソッドであり、各子クラスで実装されている(TestBedHarnessEnvironmentとProtractorHarnessEnvironment) - 今回はTestBedHarnessEnvironmentの方を見る。
-
TestbedHarnessEnvironment.createTestElement()
- TestElementクラスを継承したUnitTestElementクラスのインスタンスを生成
- constructor()で
nativeElement
,stabilize()
を受け取っている。 - このUnitTestElementクラスは
nativeElement
に対してsendKeys()
やclick()
等のイベントやTextContent等のデータを取得するクラスになっている。
-第2引数で受け取ったstabilize()
は各イベントメソッドが実行されたあとに必ず実行される関数-
() => this.forceStabilize()
と渡しているがこのforceStabilize()
を見ていくとChangeDetectionを起こしている処理だとわかる。GitHub - testbed-harness-environment.ts#L148
-
- constructor()で
- TestElementクラスを継承したUnitTestElementクラスのインスタンスを生成
-
-
- 親クラス
- 引数として受け取ったfixtureの
TestbedHarnessEnvironment.loaderについてわかったこと
- loader()は TestbedHarnessEnvironmentクラスのインスタンスを返却する
- このインスタンスのプロパティにはfixture.nativeElementをRootとするUnitTestElementが入っている。
- このUnitTestElementはその渡されたElementに対してイベントやデータの取得などの操作をするクラスである。
- また各イベントが実行されるとChangeDetectionが走る。
- このインスタンスのプロパティにはfixture.nativeElementをRootとするUnitTestElementが入っている。
ついでに
-
documentRootLoader()
を見るとloader()
ではfixture.nativeElement
を渡していたところをdobument.body
を渡している。
HarnessEnvironment.getHarnessを読んで見る
- 引数として
HarnessQuery<T>
を受け取っている。- 公式ドキュメントを見ると作成した(または用意された)Harnessを引数として渡している。
-
HarnessQuery
はComponentHarnessConstructor
またはHarnessPredicate
GitHub - component-harness.ts#L25 - ※今回は
ComponentHarnessConstructor
の場合で見ていく
-
-
locatorFor
の引数に渡しAsyncFactoryFn
を受け取っている。-
AsyncFactoryFn
は() => Promise<T>
GitHub - component-harness.ts#L13
-
- _assterResultFound()
- Promiseの結果の0番目を取得し、それがundefinedでなかったらその値を返却、undefinedであったら例外を投げる関数。
- Harnessenvironment._getAllHarnessAndTestElements()
- harness-environment.ts#L153
-
_parseQueries()
でQueriesをパース。if (typeof query === 'string')
ではない('function'になる)ためHarnessPredicateインスタンスを生成。- 結果、
harnessQueries
にPredicateが格納される。これが後に使用される。
- 結果、
-
this.getAllRawElements()
- 引数で
...harnessQueries.map(predicate => predicate.getSelector())
という処理がある。このgetSelector()
は、harnessType.hostSelector
を取得している。- だからHarnessを作成する際には
hostSelector
という変数名で設定する必要がある。(型定義されているのもあるが。) - ここでComponent Harnessの
hostSelector
すなわちComponentのタグ名を取得している。
- だからHarnessを作成する際には
- タグ名を取得し終わったら
getAllRawElements()
へ。このメソッドの実装はTestbedHarnessEnvironment.ts#L188に存在する。- このメソッドの引数には先程取得した
hostSelector
が入ってくる。 - ChangeDetectionを走らせてから要素を取得する。
- このメソッドの引数には先程取得した
- 引数で
- このあとは取得できた要素から
UnitTestElement
インスタンスを取得する - 最後に
_assertResultFound()
メソッドで結果の0番目を取得しあったらその結果を、undefinedだったら例外を投げる。- Fixtureを
getHarness()
に渡すと最後で取得できないのでは…?- やはりそうだった。
this.getAllRawElements()
で要素にFixtureのタグの要素が存在しない。これがドキュメントの -
because Angular does not set the proper tag name when creating the fixture.
- の意味だった。
- やはりそうだった。
- Fixtureを
- 公式ドキュメントを見ると作成した(または用意された)Harnessを引数として渡している。
実際読んでみると確かにFixtureのタグは要素に存在しなかった。よくよく考えてみたらごく当たり前のことだった。
しかし詳細のソースコードを読むことで「なぜこのときはこれを使うのか」という根拠が自分の中にできた。
この調査でFixtureでgetHarness()しようとしたときに出るエラー
Error: Failed to find element matching one of the following queries:
(SomeHarness with host element matching selector: "app-some")
がなぜ表示される理由のかがわかった。