統合テストとは何ですか?

統合テストは、異なる部分がグループとしてテストされるテストの一種として定義されます。私たちの場合、テストしたい部分は、特定のブロックまたはエディターロジックのレンダリングに必要な異なるコンポーネントです。最終的には、これらはユニットテストと非常に似ており、Jestライブラリを使用して同じコマンドで実行されます。主な違いは、統合テストでは、エディターが異なるコンポーネントをどのようにレンダリングするかをテストするために特定のライブラリreact-native-testing-libraryを使用することです。

統合テストの構造

テストは以下の部分で構成できます:

次のセクションでは、一般的なタスクの例やヒントも含めています:

セットアップ

この部分は通常、Jestのコールバック[beforeAll]と[beforeEach]を使用してカバーされます。目的は、ブロックの登録やロジックの一部をモックするなど、テストが必要とするすべてを準備することです。

すべてのコアブロックが利用可能であることを期待する場合の一般的なパターンの例を示します:

  1. beforeAll( () => {
  2. // Register all core blocks
  3. registerCoreBlocks();
  4. } );

レンダリング

テストロジックを導入する前に、テストしたいコンポーネントをレンダリングする必要があります。スコープ付きコンポーネントを使用するか、全体のエディターアプローチを使用するかによって、この部分は異なります。

スコープ付きコンポーネントアプローチの使用

カバー ブロックをレンダリングする例([https://github.com/WordPress/gutenberg/blob/86cd187873984f80ddeeec3e82454b486dd1860f/packages/block-library/src/cover/test/edit.native.js#L82-L91]から抽出):

  1. // This import points to the index file of the block
  2. import { metadata, settings, name } from '../index';
  3. ...
  4. const setAttributes = jest.fn();
  5. const attributes = {
  6. backgroundType: IMAGE_BACKGROUND_TYPE,
  7. focalPoint: { x: '0.25', y: '0.75' },
  8. hasParallax: false,
  9. overlayColor: { color: '#000000' },
  10. url: 'mock-url',
  11. };
  12. ...
  13. // Simplified tree to render Cover edit within slot
  14. const CoverEdit = ( props ) => (
  15. <SlotFillProvider>
  16. <BlockEdit isSelected name={ name } clientId={ 0 } { ...props } />
  17. <BottomSheetSettings isVisible />
  18. </SlotFillProvider>
  19. );
  20. const { getByText, findByText } = render(
  21. <CoverEdit
  22. attributes={ {
  23. ...attributes,
  24. url: undefined,
  25. backgroundType: undefined,
  26. } }
  27. setAttributes={ setAttributes }
  28. />
  29. );

全体のエディターアプローチの使用

ボタン ブロックをレンダリングする例([https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/buttons/test/edit.native.js#L32-L39]から抽出):

  1. const initialHtml = `<!-- wp:buttons -->
  2. <div class="wp-block-buttons"><!-- wp:button {"style":{"border":{"radius":"5px"}}} -->
  3. <div class="wp-block-button"><a class="wp-block-button__link" style="border-radius:5px" >Hello</a></div>
  4. <!-- /wp:button --></div>
  5. <!-- /wp:buttons -->`;
  6. const { getByLabelText } = initializeEditor( {
  7. initialHtml,
  8. } );

要素のクエリ

コンポーネントがレンダリングされたら、それらをクエリする時間です。このトピックに関する重要な注意点は、ユーザーの視点からテストする必要があるということです。これは、理想的には、ユーザーがアクセスできるテキストやアクセシビリティラベルなどの要素でクエリする必要があることを意味します。

クエリする際は、次の優先順位に従う必要があります:

以下は、いくつかの例です:

  1. const mediaLibraryButton = getByText( 'WordPress Media Library' );
  1. const missingBlock = getByLabelText( /Unsupported Block\. Row 1/ );
  1. const radiusSlider = getByTestId( 'Slider Border Radius' );

これらのクエリには、プレーンな文字列または正規表現を渡すことができます。正規表現は、文字列の一部をクエリするのに最適です(例:アクセシビリティラベルにUnsupported Block. Row 1を含む任意の要素)。.のような特殊文字はエスケープする必要があります。

findクエリの使用

コンポーネントをレンダリングしたりイベントを発火させたりした後、潜在的な状態の更新により副作用が発生する可能性があるため、探している要素がまだレンダリングされていない場合があります。この場合、要素が利用可能になるのを待つ必要があり、その目的のために、find*バージョンのクエリ関数を使用できます。これらは内部でwaitForを使用し、要素が表示されたかどうかを定期的にチェックします。

以下は、いくつかの例です:

  1. const mediaLibraryButton = await findByText( 'WordPress Media Library' );
  1. const missingBlock = await findByLabelText( /Unsupported Block\. Row 1/ );
  1. const radiusSlider = await findByTestId( 'Slider Border Radius' );

ほとんどの場合、find*関数を使用しますが、要素が利用可能になるのを待つ必要があるクエリに制限することが重要です。

withinクエリ

他の要素に含まれる要素をwithin関数を介してクエリすることも可能です。以下はその例です:

  1. const missingBlock = await findByLabelText( /Unsupported Block\. Row 1/ );
  2. const translatedTableTitle = within( missingBlock ).getByText( 'Tabla' );

イベントの発火

要素をクエリすることと同じくらい重要なのは、ユーザーのインタラクションをシミュレートするためにイベントをトリガーすることです。その目的のために、fireEvent関数を使用できます(ドキュメント)。

プレスイベントの例:

プレスイベント:

  1. fireEvent.press( settingsButton );

任意のタイプのイベント、カスタムイベントをトリガーすることもできます。以下の例では、スライダーコンポーネントのonValueChangeイベントをトリガーする方法を示しています(コードリファレンス

カスタムイベント – onValueChange:

  1. fireEvent( heightSlider, 'valueChange', '50' );

正しい要素の動作を期待する

要素をクエリし、イベントを発火させた後、ロジックが期待通りに機能することを確認する必要があります。その目的のために、ユニットテストで使用するのと同じexpect関数を使用できます。要素が定義されていること、有効なReact要素であること、可視であることを確認するために、カスタムtoBeVisibleマッチャーを使用することをお勧めします。

以下はその例です:

  1. const translatedTableTitle = within( missingBlock ).getByText( 'Tabla' );
  2. expect( translatedTableTitle ).toBeVisible();

さらに、エディター全体をレンダリングする際に、HTML出力が期待通りであるかどうかも確認できます:

  1. expect( getEditorHtml() ).toBe(
  2. '<!-- wp:spacer {"height":50} -->\n<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>\n<!-- /wp:spacer -->'
  3. );

クリーンアップ

最後に、次のテストに影響を与える可能性のある変更をクリーンアップする必要があります。以下は、すべてのブロックを登録解除することを含む、ブロック登録後の典型的なクリーンアップの例です:

  1. afterAll( () => {
  2. // Clean up registered blocks
  3. getBlockTypes().forEach( ( block ) => {
  4. unregisterBlockType( block.name );
  5. } );
  6. } );

ヘルパー

ネイティブバージョンの統合テストを簡単に書くための精神で、このREADMEにヘルパー関数のリストがあります。

一般的なフロー

ブロックをクエリする

ブロックをクエリする一般的な方法は、そのアクセシビリティラベルによるものです。以下はその例です:

  1. const spacerBlock = await waitFor( () =>
  2. getByLabelText( /Spacer Block\. Row 1/ )
  3. );

ブロックのアクセシビリティラベルに関する詳細情報は、関数getAccessibleBlockLabelのコードを確認できます。

ブロックを追加する

段落ブロックを挿入する方法の例を示します:

  1. // Open the inserter menu
  2. fireEvent.press( await findByLabelText( 'Add block' ) );
  3. const blockList = getByTestId( 'InserterUI-Blocks' );
  4. // onScroll event used to force the FlatList to render all items
  5. fireEvent.scroll( blockList, {
  6. nativeEvent: {
  7. contentOffset: { y: 0, x: 0 },
  8. contentSize: { width: 100, height: 100 },
  9. layoutMeasurement: { width: 100, height: 100 },
  10. },
  11. } );
  12. // Insert a Paragraph block
  13. fireEvent.press( await findByText( `Paragraph` ) );

ブロック設定を開く

ブロックを選択した後、「設定を開く」ボタンをタップすることでブロック設定にアクセスできます。以下はその例です:

  1. fireEvent.press( block );
  2. const settingsButton = await findByLabelText( 'Open Settings' );
  3. fireEvent.press( settingsButton );

スコープ付きコンポーネントアプローチの使用

スコープ付きコンポーネントアプローチを使用する場合、最初にSlotFillProviderBottomSheetSettingsをレンダリングする必要があります(isVisibleプロパティを渡してボトムシートを表示させることに注意してください)。

  1. <SlotFillProvider>
  2. <BlockEdit isSelected name={ name } clientId={ 0 } { ...props } />
  3. <BottomSheetSettings isVisible />
  4. </SlotFillProvider>

例を参照してください:

FlatListアイテム

  1. 以下は、挿入メニューでブロックリストをレンダリングするために使用されるFlatListの例です:
  2. ``````bash
  3. const blockList = getByTestId( 'InserterUI-Blocks' );
  4. // onScroll event used to force the FlatList to render all items
  5. fireEvent.scroll( blockList, {
  6. nativeEvent: {
  7. contentOffset: { y: 0, x: 0 },
  8. contentSize: { width: 100, height: 100 },
  9. layoutMeasurement: { width: 100, height: 100 },
  10. },
  11. } );
  12. `

スライダー

ボトムシートにあるスライダーは、testIDを使用してクエリする必要があります:

  1. const radiusSlider = await findByTestId( 'Slider Border Radius' );
  2. fireEvent( radiusSlider, 'valueChange', '30' );

スライダーのtestIDは「スライダー」+ラベルです。したがって、「ボーダー半径」というラベルのスライダーの場合、testIDは「スライダー ボーダー半径」となります。

内部ブロックの選択

ブロックを追加する際の注意点は、内部ブロックを含む場合、これらの内部ブロックはレンダリングされないことです。以下の例は、ボタンブロックがその内部ボタンブロックをレンダリングする方法を示しています(buttonsBlockとしてボタンブロックへの参照をすでに取得していると仮定します):

  1. const innerBlockListWrapper = await within( buttonsBlock ).findByTestId(
  2. 'block-list-wrapper'
  3. );
  4. fireEvent( innerBlockListWrapper, 'layout', {
  5. nativeEvent: {
  6. layout: {
  7. width: 100,
  8. },
  9. },
  10. } );
  11. const buttonInnerBlock = await within( buttonsBlock ).findByLabelText(
  12. /Button Block\. Row 1/
  13. );
  14. fireEvent.press( buttonInnerBlock );

ツール

アクセシビリティインスペクターの使用

要素の識別子を見つけるのに問題がある場合は、Xcodeのアクセシビリティインスペクターを使用することをお勧めします。ほとんどの識別子はクロスプラットフォームであるため、テストはデフォルトでAndroidで実行されますが、アクセシビリティインスペクターを使用して正しい識別子を見つけることができます。

Xcodeアクセシビリティインスペクターアプリのスクリーンショット。スクリーンショットは、デバイスのドロップダウンで正しいターゲットを選択し、ターゲットモードを有効にし、画面要素をタップした後にアクセシビリティラベルを見つける方法を示しています

一般的な落とし穴と注意点

waitFor関数の前にawaitを省略した場合の偽陽性

  1. <a name="waitfor-timeout"></a>
  2. ### waitForタイムアウト
  3. `````waitFor`````関数のデフォルトタイムアウトは1000msに設定されているため、これまでのところ、この値はテストしているすべてのレンダーロジックに十分です。ただし、テスト中に要素のレンダリングにもっと時間がかかることに気付いた場合は、増やす必要があります。
  4. <a name="replace-current-ui-unit-tests"></a>
  5. ### 現在のUIユニットテストの置き換え
  6. 一部のコンポーネントには、コンポーネントのレンダリングをカバーするユニットテストがすでにありますが、必須ではありません。これらの場合、統合テストへの移行の可能性を分析することが望ましいです。
  7. 両方を保持する必要がある場合は、名前の衝突を避けるために、統合テストファイルに「integration」という単語を追加します。以下はその例です:[packages/block-library/src/missing/test/edit-integration.native.js](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/missing/test/edit-integration.native.js)。
  8. <a name="platform-selection"></a>
  9. ### プラットフォームの選択
  10. デフォルトでは、すべてのテストはJestでAndroidプラットフォームを使用して実行されるため、異なるプラットフォームに関連する特定の動作をテストする必要がある場合は、プラットフォームテストファイルをサポートする必要があります。
  11. プラットフォームオブジェクトによって制御されるロジックのみをテストする必要がある場合は、次のコードを使用してモジュールをモックできます(この場合、プラットフォームをiOSに変更します):
  12. ``````bash
  13. jest.mock( 'Platform', () => {
  14. const Platform = jest.requireActual( 'Platform' );
  15. Platform.OS = 'ios';
  16. Platform.select = jest.fn().mockImplementation( ( select ) => {
  17. const value = select[ Platform.OS ];
  18. return ! value ? select.default : value;
  19. } );
  20. return Platform;
  21. } );
  22. `