Closed10

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とは?

  • 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のようだ。
  • 上記の説明からComponentFixuture自身もHarnessの役目を持ち、テスト上で要素やComponent Classを操作する際にこのFixtureが提供するAPIを使用して操作する。

適切な「タグ」とは?

  • ngc してみてもいまいち分からんかったので後回し
  • (調査した結果…)そのまま。<body> とか <div> みたいなやつをタグと言う。
    • Angularのコンポーネントだと <app-header> みたいな感じ。

なぜ「適切なタグ」が無いといけない?

  • Fixtureで保持しているnativeElementからHarnessで指定された hostSelector を使って要素を取得しにいく。
    • その取得した要素からHarnessインスタンスを作成する。
      • が、FixtureつまりそのContainer Component自体には自身のタグは存在しない。(.component.tsselector: 'app-hoge' で宣言されている。)
        • そのためnativeElementから取得できず、最後の判定処理でundefinedになるため、例外が投げられる。
        • だから「適切なタグ」が無いため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() は各イベントメソッドが実行されたあとに必ず実行される関数

TestbedHarnessEnvironment.loaderについてわかったこと

  • loader()は TestbedHarnessEnvironmentクラスのインスタンスを返却する
    • このインスタンスのプロパティにはfixture.nativeElementをRootとするUnitTestElementが入っている。
      • このUnitTestElementはその渡されたElementに対してイベントやデータの取得などの操作をするクラスである。
    • また各イベントが実行されるとChangeDetectionが走る。

ついでに

  • documentRootLoader() を見ると loader() では fixture.nativeElement を渡していたところを dobument.body を渡している。

HarnessEnvironment.getHarnessを読んで見る

  • 引数として HarnessQuery<T> を受け取っている。
    • 公式ドキュメントを見ると作成した(または用意された)Harnessを引数として渡している。
      • HarnessQueryComponentHarnessConstructor または HarnessPredicate GitHub - component-harness.ts#L25
      • ※今回は ComponentHarnessConstructor の場合で見ていく
    • locatorFor の引数に渡し AsyncFactoryFn を受け取っている。
    • _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のタグ名を取得している。
      • タグ名を取得し終わったら 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のタグは要素に存在しなかった。よくよく考えてみたらごく当たり前のことだった。
しかし詳細のソースコードを読むことで「なぜこのときはこれを使うのか」という根拠が自分の中にできた。

この調査でFixtureでgetHarness()しようとしたときに出るエラー

Error: Failed to find element matching one of the following queries:
(SomeHarness with host element matching selector: "app-some")

がなぜ表示される理由のかがわかった。

このスクラップは2021/02/09にクローズされました
ログインするとコメントできます