2023.05.31[Wed]

JestでIntersectionObserverをモックする方法

  • TypeScript
  • Jest
  • React
  • テスト

目次

  • - はじめに
  • - 環境
  • - 実装
  • - 実装方法
  • - 実行結果
  • - 注意点
  • - まとめ

はじめに

こんにちは、Webエンジニアの柴田です。
今回はJestでIntersectionObserverをモックする方法をご紹介します。
IntersectionObserverをモックすることで、遅延レンダリングのテストや遅延レンダリングを応用した要素のテストなどができます。

環境

Jest: 27.5.1
TypeScript: 4.6.4
@testing-library/react: 12.1.4

実装

実装方法

Jestのjest.fn関数を用いて、IntersectionObserverのモックを実装します。
IntersectionObserverのモックとして定義した関数が呼ばれているかどうかを確認するテストを書きます。

jest.fn関数とは、元の関数の実装をモック関数で上書きする関数です。
今回は返り値がnullになっていますが、任意に変えることもできます。

const mockObserve = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
const mockDisconnect = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
const mockUnObserve = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
const mockIntersectionObserver = class {
  constructor() {}

  observe = mockObserve;

  disconnect = mockDisconnect;

  unobserve = mockUnObserve;
};

定義したモックをIntersectionObserverにセットします。

(global as any).IntersectionObserver = mockIntersectionObserver;

遅延レンダリングを実装したComponentを用意します。
ダミーの商品データを元に、レンダリングします。
レンダリングする前にモックした関数が呼ばれていないことも事前に確認します。

const Cards = mockProductsData.map(({ product }, index) => (
  <Card
    index={index}
    className={''}
    product={product}
    key={product.id}
  />
));
const App = (
  <div>
    {Cards}
  </div>
)

// レンダリングする前はモックした関数(例:mockObserve)が呼ばれていないことを確認
expect(mockObserve).not.toHaveBeenCalled();
expect(mockObserve).toHaveBeenCalledTimes(0);

const { container } = render(App);

First Viewの外までスクロールします。
そして、モックした関数(例: mockObserve)が呼ばれていることを確認します。

// First View(画面)外の要素までスクロールする
// [data-testid="firsViewElement-out-30"]はFirst View外の要素のうちの一つです。
fireEvent.scroll(container.querySelector('[data-testid="firsViewElement-out-30"]')!, { target: { scrollY: 500 } })

// モックした関数(例:mockObserve)が呼び出されたかどうかテスト
expect(mockObserve).toHaveBeenCalled();

実行結果

無事テストが通りました。

注意点

モックのクリーンアップの記述を忘れずに書きましょう。
他のテストの結果に影響を与えないためです。

jest.restAllMocks()
モックした全ての関数を元の関数の実装に戻します。

afterAll(() => {
  jest.resetAllMocks();
});

まとめ

以上、JestでIntersectionObserverをモックする方法をご紹介しました。

今回記事でご紹介したコードのフルバージョン
describe('IntersectionObserverをモック', () => {
  afterAll(() => {
    // リセット処理
    jest.resetAllMocks();
  });
  // jest.fnでテスト
  test('jest.fnを用いて。IntersectionObserverをモックしたテストができているか', async() => {
    const mockObserve = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
    const mockDisconnect = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
    const mockUnObserve = jest.fn<null, [undefined, null]>().mockImplementation(() => { return null });
    const mockIntersectionObserver = class {
      constructor() {}

      observe = mockObserve;

      disconnect = mockDisconnect;

      unobserve = mockUnObserve;
    };
    // セット
    (global as any).IntersectionObserver = mockIntersectionObserver;

    const Cards = mockProductsData.map(({ product }, index) => (
      <Card
        index={index}
        className={''}
        product={product}
        key={product.id}
      />
    ));
    const App = (
      <div>
        {Cards}
      </div>
    )

    // レンダリングする前はモックした関数(例:mockObserve)が呼ばれていないことを確認
    expect(mockObserve).not.toHaveBeenCalled();

    const { container } = render(App);

    // 画面外の要素までスクロールする
    fireEvent.scroll(container.querySelector('[data-testid="firsViewElement-out-30"]')!, { target: { scrollY: 500 } })

    // モックした関数(例:mockObserve)が呼び出されたかどうかテスト
    expect(mockObserve).toHaveBeenCalled();
});

・関連記事
JestでspyOn関数を使って、windowをモックする方法
jest.spyOnとjest.fnの違い・使い分けについてもこちらの記事にてご紹介してあります。
https://techlab.q-co.jp/articles/90/

Share

SpeedCurve 利用例 デプロイのタイミングに線を引くSpeedCurve 利用例 Favoritesタブ編