JavaScriptStencilTypeScript

Stencilで、コンポーネントのユニットテスト

Stencilを使用してWebコンポーネントを作る場合に、テストの書き方について整理しました。サンプルコンポーネントModalの表示・非表示を制御するコンポーネントにテストを追加する方法を説明しています。@Methodと@Eventをテストする方法についても述べられています。また、スナップショットテストを使用することもできます。Jestを使ったモックについても説明があります。さらに、参考記事のリンクも掲載されているので、Stencilでテストをする際に役立つでしょう。

広告ここから
広告ここまで

StencilでWeb Componentを作ることがあるので、ユニットテストの書き方を整理しました。

テストを書くサンプルコンポーネント

モーダルの表示・非表示を制御するコンポーネントにテストを追加する想定です。

import { Component, Host, h, Prop, Method, Element, Event, EventEmitter } from '@stencil/core';

@Component({
  tag: 'demo-modal',
  styleUrl: 'demo-modal.scss',
  shadow: true,
})
export class DemoModal {
  @Element() el: HTMLDemoModalElement;

  /**
   * Modal state.
   * If true, the modal will open
   */
  @Prop() open = false;

  /**
   * Toggle modal state
   */
  @Method()
  public async toggleModal() {
    this.open = !this.open;
    if (this.open === false) {
      this.close.emit();
    }
  }

  /**
   * Open the modal
   */
  @Method()
  public async openModal() {
    this.open = true;
  }

  /**
   * Close the modal
   */
  @Method()
  public async closeModal() {
    this.open = false;
    this.close.emit();
  }

  /**
   *
   */
  @Event() close: EventEmitter;

  render() {
    const { open } = this;

    return (
      <Host>
        <div class={`modal-row${open ? ' open' : ''}`} onClick={() => this.closeModal()}>
          <div class="modal-child" onClick={e => e.stopPropagation()}>
            <slot></slot>
          </div>
        </div>
      </Host>
    );
  }
}

stencil gコマンドで生成されるテストコードはだいたいこんな感じのはずです。

import { newSpecPage } from '@stencil/core/testing';
import { DemoModal } from '../demo-modal';

describe('demo-modal', () => {
  it('renders', async () => {
    const page = await newSpecPage({
      components: [DemoModal],
      html: `<demo-modal></demo-modal>`,
    });

    expect(page.root).toEqualHtml(`
      <demo-modal></demo-modal>
    `);
  });
});

コンポーネント内のメソッド(@Method)をテストする

まずは@Methodをテストします。ここではopenModalを試しましょう。

  /**
   * Modal state.
   * If true, the modal will open
   */
  @Prop() open = false;

  /**
   * Open the modal
   */
  @Method()
  public async openModal() {
    this.open = true;
  }

メソッドなどをテストする場合は、コンポーネントを通常のクラスとして実行します。

const component = new DemoModal()

@Propsの値はPublicなので、外から変更できます。

component.open = false

同様に@Methodの呼び出しも可能です。

await component.openModal()

openModalを呼び出すと、openプロパティがfalseからtrueに変わる」をテストするコードは、こうなります。

import { DemoModal } from '../demo-modal';

describe('#openModal', () => {
  it("open props should change to true when the openModal method was called", async () => {
    const component = new DemoModal()
    component.open = false
    await component.openModal()
    expect(component.open).toEqual(true)
  })
})

@EventのEmitterをモックしてテストする

次は@Eventのテストです。

  /**
   * Close the modal
   */
  @Method()
  public async closeModal() {
    this.open = false;
    this.close.emit();
  }

  /**
   *
   */
  @Event() close: EventEmitter;

@Eventは「不必要に呼び出されていないか」などだけをテストしたいので、Jestでモックしましょう。

const component = new DemoModal()
component.close = {
  emit: jest.fn(),
}

あとはこちらも@Methodを呼び出してテストするだけです。


  it("open props should change to false when the closeModal method was called", async () => {
    const component = new DemoModal()
    component.close = {
      emit: jest.fn(),
    }
    await component.closeModal()
    expect(component.open).toEqual(false)
  })

「2回以上イベントが発火していないか」などをチェックするために、モックした@Eventをテストしましょう。

jest.fn()を一旦変数に入れておく方が、コードを読んだ時にわかりやすいかなと思います。

  it("When the closeModal method called, should called close event at once", async() => {
    const component = new DemoModal()
    component.open = true
    const mockEmitter = jest.fn()
    component.close = {
      emit: mockEmitter
    }
    await component.closeModal()
    expect(mockEmitter).toBeCalledTimes(1)
  })

スナップショットテストをする

「描画されるHTMLが意図せず変わっていないか」のテストに絞る場合は、スナップショットテストも使えます。


it("should match snapshot (open='true')", async () => {
  const page = await newSpecPage({
    components: [DemoModal],
    html: `<demo-modal open="true"></demo-modal>`,
  })
  expect(page.root).toMatchSnapshot()
})

スナップショットが生成されました。

exports[`demo-modal Rendering test should match snapshot (open='true') 1`] = `
<demo-modal open="true">
  <mock:shadow-root>
    <div class="modal-row open">
      <div class="modal-child">
        <slot></slot>
      </div>
    </div>
  </mock:shadow-root>
</demo-modal>
`;

参考記事など

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts