LitでつくったWebComponentをJestでテストする

Lit(+TypeScript)でつくったWebComponentをJestでテストしたくなったので、行ったことをまとめておきます。

前提

  • テストやコンポーネントはTypeScriptで記述
  • ts-jestが導入済み
  • Lit 2.0.0 / Jest 26系で動作確認

テストを書いてみる

以下のようなコンポーネントを定義したとします。

import { LitElement, html, css } from 'lit'
import { property, customElement } from 'lit/decorators.js'

@customElement('test-component') 
export class TestComponent extends LitElement {
  @property({type: String}) title = ''

  render() {
     return html`
     <div>
       <span>${title}</span>
      </div>
    `;
  }
}

まずは JestのDOM操作に関するドキュメント を参考にテストを書いてみます。雰囲気はこんなかんじ。

// 以下のように読み込むことで @customElement decoratorによってコンポーネントが定義される
import '~/src/TestComponent'
// 型情報がほしいので別途TestComponentを読み込む
import { TestComponent } from '~/src/TestComponent'


test('Content should include a title', async () => {
  document.body.innerHTML = `
    <test-component title="タイトル"></test-component>
  `
  const component = document.getElementsByTagName('test-component')[0] as TestComponent

  // コンテンツが描画されるまで待つ
  await component.updateComplete

  // たとえばtitle attribute が正しくコンテンツに反映されていることを確認する
  expect(component.shadowRoot?.innerHTML.match(/タイトル/g)?.length).toEqual(1)  
})

Lit ドキュメントの Lifecycleの項 をみると、コンテンツの更新が完了すると updateComplete PromiseがResolvedになるとあるので、コンテンツが更新されてからテスト項目を確認していきます(更新される前にテスト項目を確認すると、変更が反映されていない可能性があり、思うような実行結果が得られません)。

DOMの操作・取得の関数(たとえば Element.innerHTML など)がテストでも使えるので、これを用いて actual な状態を確認していきます。

もし、 @customElement decoratorを使わずにコンポーネントを定義している場合は、以下のようにテスト内で customElements.define() を呼ぶことで同様にCustomElementを定義できます。

import { TestComponent } from '~/src/TestComponent'

customElements.define('test-component', TestComponent)

// 以下同じ

テスト中の document.getElementsByTagName で取得した componentTestComponentインスタンス化したものなので、メソッドやプロパティにアクセスすることもできます。TestComponent内の何かをモックしたい~みたいなこともできます。

テストを実行できるようにする

テストを実行してみると、以下のようなエラーが出てしまいます。

 FAIL  tests/TestComponent.test.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /path/to/test-project/node_modules/lit/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import"@lit/reactive-element";import"lit-html";export*from"lit-element/lit-element.js";
                                                                                             ^^^^^^

    SyntaxError: Cannot use import statement outside a module

    > 1 | import { LitElement, html, css } from 'lit'
        | ^

Lit ライブラリ内に import 文があるので、これをJestが解釈できずにエラーになっているようです。Jestはデフォルトでは node_modules 配下のファイルをトランスパイルしないので、 エラーメッセージにある通り transformIgnorePatterns に手を加え、Lit関連ライブラリがトランスパイルされるようにします。

トランスパイルの対象はエラーメッセージのとおりjsファイルなので、 babel-jest を使ってトランスパイルされるようにします。(必要に応じて @babel/preset-env をインストールします)

JestのGetting Started を参考に、babel.config.js を以下のように記述します。

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      }
    ]
  ]
}

続いて、jest.config.js を以下のように設定します。

module.exports = {
  preset: 'ts-jest/presets/js-with-babel',
  transformIgnorePatterns: [
    "/node_modules/(?!(@lit|lit|lit-element|lit-html)/)"
  ]
}

これでテストを実行できるようになりました。めでたしめでたし。

(おまけ)Canvas要素を含むコンポーネントのテスト実行

CustomElementにCanvasに関する処理が含まれていた場合は、テストの実行に失敗することがあります。JestのDOM周りには jsdom が用いられていますが、READMEをみると Cavnas Support の項があります。 これによると、 node-canvs をインストールすればよいとのことなので試してみると、インストールして特に設定を変えることなく実行することができました。


LitのTestingの項 によると、Web Test Runner を使うのがおすすめだよ!と書いてあるので、これもおいおいさわってみよう~となっていました。ではでは~

modern-web.dev