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>
    `;

    参考記事など

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark