jest で ResizeObserver、Element.getBoundingClientRect をモックにするには

前回作成したコンポーネントのテストコードを書いてみた所、ResizeObserver と Element.getBoundingClientRect をモックにする必要がありました。
どの様にしたかを記述します。

モック化したテストコード

まず、実際にモックにしたテストコードは以下の通りです。

import React from 'react';
import { act } from 'react-dom/test-utils';
import { render, screen } from '@testing-library/react';
import { Text } from '../Text';

let instanceResize: ResizeObserver | null = null;
let callbackResize: ResizeObserverCallback | null = null;
global.ResizeObserver = class mockResizeObjerver {
  constructor(callback: ResizeObserverCallback) {
    instanceResize = this;
    callbackResize = callback;
  }
  disconnect(){ }
  observe(target: Element, options?: ResizeObserverOptions){ }
  unobserve(target: Element){ }
}

describe('Textのテスト', () => {
  test('省略表示されること', () => {
    jest.spyOn(Element.prototype, 'getBoundingClientRect')
      // rectOuter
      .mockImplementationOnce(jest.fn(() => ({x:0, y:10, width: 50, height: 50}) as DOMRect))
      // rectInner
      .mockImplementationOnce(jest.fn(() => ({x:0, y:10, width: 100, height: 50}) as DOMRect));

    const result = render(<Text>abcdefghijklmnopqrstuvwxyz</Text>);
    act(() => {
      callbackResize!([], instanceResize!);
    });
    // console.log('省略表示されること', screen.debug());
    const outer = result.container.querySelectorAll('span[title]');
    expect(outer.length).toBe(1);
  });
  test('省略表示されないこと', () => {
    const result = render(<Text>abcdefghijklmnopqrstuvwxyz</Text>);
    act(() => {
      callbackResize!([], instanceResize!);
    });
    // console.log('省略表示されないこと', screen.debug());
    const outer = result.container.querySelectorAll('span[title]');
    expect(outer.length).toBe(0);
  });
});

ResizeObserver をモックしないとどうなるか

以下の通り、「ResizeObserver は未定義」でコケます。

  ● Textのテスト › 省略表示されること

    ReferenceError: ResizeObserver is not defined

      13 |   // リサイズ監視
      14 |   useEffect(() => {
    > 15 |     const resizeObserver = new ResizeObserver(() => {
         |                            ^
      16 |       const rectOuter = refOuter.current?.getBoundingClientRect();
      17 |       const rectInner = refInner.current?.getBoundingClientRect();
      18 |       // 外枠 < 内枠 なら省略表記

ということで、以下の様にモックにします。

let instanceResize: ResizeObserver | null = null;
let callbackResize: ResizeObserverCallback | null = null;
global.ResizeObserver = class mockResizeObjerver {
  constructor(callback: ResizeObserverCallback) {
    instanceResize = this;
    callbackResize = callback;
  }
  disconnect(){ }
  observe(target: Element, options?: ResizeObserverOptions){ }
  unobserve(target: Element){ }
}

コンストラクタに渡すコールバック関数が呼ばれないとリサイズの処理がされないので、callbackResize を外出ししてテストコード中に呼んでいます。
コールバックに渡す引数に ResizeObserver のインスタンスが必要なので、こちらも instanceResize に外出ししています。

Element.getBoundingClientRect をモックしないとどうなるか

「省略表示されること」のテストでコケます。

  ● Textのテスト › 省略表示されること

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 0

      20 |     const result = render(<Text>abcdefghijklmnopqrstuvwxyz</Text>);
      21 |     const outer = result.container.querySelectorAll('span[title]');
    > 22 |     expect(outer.length).toBe(1);
         |                          ^
      23 |   });
      24 |   test('省略表示されないこと', () => {
      25 |     const result = render(<Text>abcdefghijklmnopqrstuvwxyz</Text>);

描画内容を確認する為、screen.debug() を console.log してみると、

    <body>
      <div>
        <span
          class="text"
        >
          <span>
            abcdefghijklmnopqrstuvwxyz
          </span>
        </span>
      </div>
    </body>

の通り、title 属性がありません。確認してみると、getBoundingClientRect が返してくる値が、

{ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0 }

と、全部ゼロ。試しに幅を指定した要素の子要素にText コンポーネントを入れても変わりませんでした。ということで、

    jest.spyOn(Element.prototype, 'getBoundingClientRect')
      // rectOuter
      .mockImplementationOnce(jest.fn(() => ({x:0, y:10, width: 50, height: 50}) as DOMRect))
      // rectInner
      .mockImplementationOnce(jest.fn(() => ({x:0, y:10, width: 100, height: 50}) as DOMRect));

のように、呼び出された1回目は幅50、2回目は幅100、を返すモックにして省略表示が必要となる状況にしました。

テストしている内容

省略表示した場合は、

    <body>
      <div>
        <span
          class="text"
        >
          <span
            title="abcdefghijklmnopqrstuvwxyz"
          >
            abcdefghijklmnopqrstuvwxyz
          </span>
        </span>
      </div>
    </body>

省略表示しない場合は、

    <body>
      <div>
        <span
          class="text"
        >
          <span>
            abcdefghijklmnopqrstuvwxyz
          </span>
        </span>
      </div>
    </body>

という描画結果なので、title 属性がある span の有無をテストしています。

コメント