🦁

event.preventDefault() を呼んだことをテストする

2024/03/19に公開

概要

下記のフッターで、clickイベントが実行された際のイベントを操作している。
クリックした後のイベント操作が行われたかのテストを書きたい。
この処理があるかないかで、ブラウザの挙動が変わるのでユニットテストを書くべきだと思う。

const FooterList: FC = (props) => {
  return (
    <ul>
      <li>
        <a href="/company">運営会社</a>
      </li>
      <li>
        <a href="/terms">利用規約</a>
      </li>
      <li>
        <a 
          id="test_shitai" 
          href="#" 
          onClick={(e) => {
            e.preventDefault() // ここのテストを書きたい
          }}
        >
          テストしたいリンク 
        </a>
      </li>
    </ul>
  );
};

対象読者

  • Javascriptで開発している方
  • Eventのテストがわからない方
  • Javascriptをもっと理解したい方

いいね!してね

この記事の事例は必要に応じて今後追記していく予定です!
「新しい事例が知りたい」「他の事例も知りたい」と思った人は、ぜひこの記事にいいね👍してください。筆者のモチベーションにつながります!

それでは以下が本編です。

先に結論

import { fireEvent, render } from '@testing-library/react';
import { Footer } from './Footer';

describe(Footer, () => {
  function setup() {
    return {
      user: userEvent.setup(),
      view: render(<Footer />),
    };
  }

  afterEach(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
  });

  it('のテストしたいリンクは、デフォルトの挙動を抑制したリンクタグであること', async () => {
    const preventDefaultMock = jest.fn();
    const clickEvent = new MouseEvent('click', { bubbles: true });
    Object.assign(clickEvent, { preventDefault: preventDefaultMock });

    const { view } = setup();

    const link = view.getByRole('link', { name: 'テストしたいリンク' });

    fireEvent(link, clickEvent);

    expect(link.getAttribute('id')).toEqual('test_shitai');
    expect(link.getAttribute('href')).toEqual('#');
    expect(clickEvent.preventDefault).toHaveBeenCalledTimes(1);
  });
});

説明すること

説明すること

  • イベントリスナーの実装方法を分解して考える
  • テストケースの説明する

イベントリスナーの実装方法を分解して考える

クリックした時に渡ってくるe(Event)はどこでどうやって定義して渡ってくるのか、、、?
イベントリスナーってどうなっているのか、分解して考えた。

HTMLにクリックイベントを定義する

よくみる形

<a 
  id="test_shitai" 
  href="#" 
  onClick={(e) => {
    e.preventDefault()
  }}
>
  テストしたいリンク 
</a>

addEventListenerを使う

ひと世代前のオールドスタイル

<a 
  id="test_shitai" 
  href="#" 
>
  テストしたいリンク 
</a>

<script>
  const linkElement = document.getElementById("test_shitai")
  linkElement.addEventListener("click", function(event) {
    e.preventDefault()
  });
</script>

これをカスタムイベント変数を使って分解していくと、下記になる。

<a 
  id="test_shitai" 
  href="#" 
>
  テストしたいリンク 
</a>

<script>
  // 1 要素を取得する
  const linkElement = document.getElementById("test_shitai")
  // 2 (カスタム)イベント変数を定義する
  const clickEvent = new MouseEvent('click', { bubbles: true });
  // 3 要素にイベントを追加する
  linkElement.addEventListener("click", function(e) {
  // 5 eにclickEventで渡ってくるので、preventDefault関数を実行する
    e.preventDefault()
  });

  // 4 クリックした場合(イベントディスパッチが実行される)
  // linkElement.dispatchEvent(clickEvent);
</script>

テストケースの説明する

結論で書いたspecを再掲して、コメントを追加した。

  it('のテストしたいリンクは、デフォルトの挙動を抑制したリンクタグであること', async () => {
    // 上記「 2 (カスタム)イベント変数を定義する」 の際に、
    // preventDefault関数をモック関数と差し替える。
    const preventDefaultMock = jest.fn();
    const clickEvent = new MouseEvent('click', { bubbles: true });
    Object.assign(clickEvent, { preventDefault: preventDefaultMock });

    const { view } = setup();

    const link = view.getByRole('link', { name: 'テストしたいリンク' });

    // 上記「4 クリックした場合(イベントディスパッチが実行される)」を実行している
    fireEvent(link, clickEvent);

    // 上記「5 eにclickEventで渡ってくるので、preventDefault関数を実行する」で
    // モック関数が実行されたことを検証する
    expect(clickEvent.preventDefault).toHaveBeenCalledTimes(1);
    expect(link.getAttribute('id')).toEqual('test_shitai');
    expect(link.getAttribute('href')).toEqual('#');
  });

まとめ

よくあるテストケースだと、下記のようなケースがある(今回のケースをググるときにヒットしまくった)

  • clickしたら色が変わったか?( -> clickした後にcssの値を検証するだけ )
  • clickしたら数値が変わったか?( -> clickした後に画面に表示される値を検証するだけ )
  • clickしたか?( -> propsで渡すだけ)

これらは、自分で実装した箇所なのでテストしやすいし、デグレに気づきやすい。

今回のテストは、

  • デグレしたらダルい、気づきづらい
  • JSの組込関数(Event)を理解する必要がある(理解していない人はテストを書けない&書かない)

のでこういう細かいところまでテストを書いて、JSへの理解を深めていきたい。

参考や引用

カスタムイベントのディスパッチについて詳しく知りたい方はどうぞ!
https://ja.javascript.info/dispatch-events

ライブラリ(Swiperなど)のイベント操作(click, mousedown)のユニットテストも書けそう。
※時間があれば書きたい
https://github.com/fex-team/swiper/blob/master/tests/swiper.spec.ts

Discussion