2020年の反応のためにテストを書く方法-パート2


Writing React Test with React recommend libraries - Jest & Testing Library for React Intermediate users.



ご注意ください
この記事では、私は反応テストでより高度な概念を探索する、私はあなたの状況に役立つと思います.あなたが反応の初心者であるか、テストに新しいならば、私はあなたが継続する前に若干の基本的な知識があることを確認するように提案します、ありがとう!

まず、アクセシビリティテストを見てみましょう.
フロントエンドの開発は、すべての可視化とエンドユーザーとの相互作用、アクセシビリティテストは、我々のアプリができるだけ多くのユーザーに到達できるようにすることができます.

からhttps://reactjs.org/docs/accessibility.html
あなたのアプリケーションのあらゆる側面のための書き込みアクセシビリティのテストは非常に脅迫的だが、ありがとうDeque Systems - 提供することによってソフトウェアアクセシビリティを改善することに捧げられる会社Axe テストパッケージは自由にオンラインで、我々はすぐにインポートすることによって世界中の多くのシニア開発者から専門知識を活用することができますJest-axe Jestライブラリと一緒にWebアプリケーションのアクセシビリティをテストする.
npm install --save-dev jest-axe
or
yarn add --dev jest-axe
パッケージをインストールすると、アクセシビリティテストをプロジェクトに追加できます.
// App.test.js
import React from 'react';
import App from './App';
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);

describe('App', () => {
  test('should have no accessibility violations', async () => {
    const { container } = render(<App />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});
それはあなたのフロントエンドの開発を保証するのに役立ちますWCAG(Web Content Accessibility Guidelines) . たとえば、ナビゲーションバーコンポーネントに間違ったロールを割り当てる場合は、
// ./components/navBar.js
...
<div className="navbar" role='nav'>
   ...
</div>
...
次のように警告します.

Check out a List of WAI-ARIA Roles Here.


NAVをナビゲーションの役割に置き換え、テストはパスします.
// ./components/navBar.js
...
<div className="navbar" role='navigation'>
   ...
</div>
...
我々は上記を見ることができるように、このテストでは、次のことを確認するのに役立ちますWCAG(Web Content Accessibility Guidelines) あなたのアプリがそこに人々のほとんどに到達できるように標準.

第二に、スナップショットのテストを追加します.

Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly. -- From Jest


あなたは全体のアプリケーションまたは1つの特定のコンポーネントにテストを置くことができます.彼らは開発サイクルの間に異なる目的を果たすことができます、あなたのアプリケーションのUIが時間とともに変化しないか、現在の出力で最後のスナップショットの違いを比較することを確実にするためにスナップショットテストを使用することができます.
全体のアプリのテストを書く方法のスナップショットテストを書く方法を示すためにしましょう.
// App.test.js
import React from 'react';
import App from './App';

import renderer from 'react-test-renderer';
...

describe('App', () => {
  ...

  test('snapShot testing', () => {
    const tree = renderer.create(<App />).toJSON();
    expect(tree).toMatchSnapshot();
  });

});
このテストが初めて実行されると、Jestはスナップショットファイル(フォルダ)を作成します__snapshots__ "create createも同様です.

// App.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`App snapShot testing 1`] = `
<div
  className="App"
>
  <div
    className="navbar"
  >
    ....
このテストでは、DOMを変更すると、テストは失敗し、以下の出力のように、prettified形式で変更されたものを正確に表示します.

この場合、どちらかを押すことができますu スナップショットを更新するか、コードを変更してテストを再度パスします.

If you adding a snapshot test at the early stage of development, you might want to turn off the test for a while by adding x in front of the test, to avoid getting too many errors and slowing down the process.


 xtest('should have no accessibility violations', async () => {
   ...
  });

第三に、API呼び出しでUIをテストする方法を見てみましょう.
フロントエンドUIは、そのページをレンダリングする前にAPIからいくつかのデータを取得しなければなりません.今日のフロントエンドの開発にとって、それに関するテストはもっと重要になります.
まず、その過程を見て、どうやってテストできるか考えてみましょう.
  • 条件が満たされたとき(ボタンやページをロードするなど)、APIの呼び出しがトリガされます
  • データがAPIから戻るとき、通常、応答は次のステップ(オプション)に行く前に解析する必要があります
  • 適切なデータを持つ場合、ブラウザはデータをレンダリングし始めます.
  • 一方、何かがうまくいかない場合は、ブラウザでエラーメッセージが表示されます.
  • フロントエンドの開発では、以下のようにテストできます.
  • 応答が正しく解析されて戻ってくるかどうか?
  • データが正しく正しい場所にブラウザでレンダリングされているかどうか?
  • 何かが間違っているときにブラウザがエラーメッセージを表示するかどうか?
  • しかし、我々はそうするべきではありません.
  • APIコールをテストする
  • テスト用の本当のAPIを呼び出します
  • Because most of the time, API is hosted by the third party, the time to fetch data is uncontrollable. Besides, for some APIs, given the same parameters, the data come back may vary, which will make the test result unpredictable.


    APIをテストするには、次のようにします.
  • Fockデータをテストして返します
  • 偽のデータを使用して一致するかどうかを確認するUI要素を比較する
  • あなたがアイデアを得た場合は、実際のコードの練習に飛び込みましょう.
    次のニュースページのコンポーネントをテストしたいと思いますgetNews APIの呼び出しとブラウザでレンダリングします.
    // ./page/News.js
    import React, { useState, useEffect } from 'react';
    import getNews from '../helpers/getNews';
    import NewsTable from '../components/newsTable';
    
    export default () => {
      const [posts, setPosts] = useState([]);
      const [loading, setLoading] = useState(true);
      const [errorMsg, setErrorMsg] = useState('');
      const subreddit = 'reactjs';
    
      useEffect(() => {
        getNews(subreddit)
          .then(res => {
            if (res.length > 0) {
              setPosts(res);
            } else {
              throw new Error('No such subreddit!');
            }
          })
          .catch(e => {
            setErrorMsg(e.message);
          })
          .finally(() => {
            setLoading(false);
          });
      }, [])
    
      return (
        <>
          <h1>What is News Lately?</h1>
          <div>
            {loading && 'Loading news ...'}
            {errorMsg && <p>{errorMsg}</p>}
            {!errorMsg && !loading && <NewsTable news={posts} subreddit={subreddit} />}
          </div>
        </>
      )
    }
    
    まず、作成しましょう__mocks__ API呼び出しファイルが存在する場所のフォルダ.(例では、API呼び出しファイル呼び出しgetNews.js ), このフォルダに同じ名前を持つ模擬API呼び出しファイルを作成します.最後に、このフォルダの中にいくつかの模擬データを準備します.

    模擬APIファイルgetNews.js ) 以下のようにsthを見るべきです.
    // ./helpers/__mocks__/getNews.js
    import mockPosts from './mockPosts_music.json';
    
    // Check if you are using the mock API file, can remove it later
    console.log('use mock api'); 
    
    export default () => Promise.resolve(mockPosts);
    
    リアルAPIコール
    // ./helpers/getNews.js
    import axios from 'axios';
    import dayjs from 'dayjs';
    
    // API Reference - https://reddit-api.readthedocs.io/en/latest/#searching-submissions
    
    const BASE_URL = 'https://api.pushshift.io/reddit/submission/search/';
    
    export default async (subreddit) => {
      const threeMonthAgo = dayjs().subtract(3, 'months').unix();
      const numberOfPosts = 5;
    
      const url = `${BASE_URL}?subreddit=${subreddit}&after=${threeMonthAgo}&size=${numberOfPosts}&sort=desc&sort_type=score`;
    
      try {
        const response = await axios.get(url);
        if (response.status === 200) {
          return response.data.data.reduce((result, post) => {
            result.push({
              id: post.id,
              title: post.title,
              full_link: post.full_link,
              created_utc: post.created_utc,
              score: post.score,
              num_comments: post.num_comments,
              author: post.author,
            });
            return result;
          }, []);
        }
      } catch (error) {
        throw new Error(error.message);
      }
      return null;
    };
    
    上記のコードからわかるようにmock API call ただ単に解決された模擬データを返しますreal API call オンラインでテストを実行するたびにデータを取得する必要があります.
    模擬APIと模擬データを準備すると、今テストを開始します.
    // ./page/News.test.js
    import React from 'react';
    import { render, screen, act } from '@testing-library/react';
    import { BrowserRouter as Router } from "react-router-dom";
    import News from './News';
    
    jest.mock('../helpers/getNews');  //adding this line before any test.
    
    // I make this setup function to simplify repeated code later use in tests.
    const setup = (component) => (
      render(
       // for react-router working properly in this component
      // if you don't use react-router in your project, you don't need it.
        <Router>
          {component}
        </Router>
      )
    );
    
    ...
    

    Please Note:


    jest.mock('../helpers/getNews');
    

    Please add the above code at the beginning of every test file that would possibly trigger the API call, not just the API test file. I make this mistake at the beginning without any notifications, until I add console.log('call real API') to monitor calls during the test.


    次に、タイトルと読み込みメッセージが正しく表示されているかどうかをチェックする簡単なテストを書き始めます.
    // ./page/News.test.js
    ...
    describe('News Page', () => {
      test('load title and show status', async () => {
        setup(<News />);  //I use setup function to simplify the code.
        screen.getByText('What is News Lately?'); // check if the title show up
        await waitForElementToBeRemoved(() => screen.getByText('Loading news ...'));
      });
    ...
    });
    

    と呼ばれるmock APIとページレンダリングを期待します.もっと複雑なテストを書き続けることができます.
    ...
    test('load news from api correctly', async () => {
        setup(<News />);
        screen.getByText('What is News Lately?');
    
        // wait for API get data back
        await waitForElementToBeRemoved(() => screen.getByText('Loading news ...'));
    
        screen.getByRole("table");  //check if a table show in UI now
        const rows = screen.getAllByRole("row");  // get all news from the table
    
        mockNews.forEach((post, index) => {
          const row = rows[index + 1];  // ignore the header row
    
           // use 'within' limit search range, it is possible have same author for different post
          within(row).getByText(post.title);  // compare row text with mock data 
          within(row).getByText(post.author); 
        })
    
        expect(getNews).toHaveBeenCalledTimes(1); // I expect the Mock API only been call once
        screen.debug(); // Optionally, you can use debug to print out the whole dom
      });
    ...
    

    Please Note


     expect(getNews).toHaveBeenCalledTimes(1);
    

    This code is essential here to ensure the API call is only called as expected.


    このAPIの呼び出しテストに応じてパスすると、我々は何かもっとエキサイティングな探索を開始することができます!
    私たちが知っているように、APIの呼び出しは時々様々な理由のために間違って行くことができる、どのように我々はそれをテストするつもりですか?
    そのためには、最初に私たちのモックAPIファイルを書き直す必要があります.
    // // ./helpers/__mocks__/getNews.js
    console.log('use mock api');  // optionally put here to check if the app calling the Mock API
    // check more about mock functions at https://jestjs.io/docs/en/mock-function-api
    const getNews = jest.fn().mockResolvedValue([]); 
    export default getNews;
    
    それから、セットアップ機能を再書き込みする必要がありますNews.test.js ファイル.
    // ./page/News.test.js
    ...
    // need to import mock data and getNews function
    import mockNews from '../helpers/__mocks__/mockPosts_music.json';
    import getNews from '../helpers/getNews';
    ...
    // now we need to pass state and data to the initial setup
    const setup = (component,  state = 'pass', data = mockNews) => {
      if (state === 'pass') {
        getNews.mockResolvedValueOnce(data);
      } else if (state === 'fail') {
        getNews.mockRejectedValue(new Error(data[0]));
      }
    
      return (
        render(
          <Router>
            {component}
          </Router>
        ))
    };
    ...
    
    デフォルト値をここで設定関数に渡しますので、前のテストを変更する必要はありません.しかし、私はテストではなく、テストを通過することをお勧めします.
    さあ、API失敗のテストを書きましょう.
    // ./page/News.test.js
    ...
    test('load news with network errors', async () => {
        // pass whatever error message you want here.
        setup(<News />, 'fail', ['network error']);
        screen.getByText('What is News Lately?');
    
        await waitForElementToBeRemoved(() => screen.getByText('Loading news ...'));
        screen.getByText('network error');
    
        expect(getNews).toHaveBeenCalledTimes(1);
      })
    ...
    
    最後に、から完全なテストコードを見つけることができますhere .

    Please Note
    They are just simple test cases for demonstration purposes, in the real-world scenarios, the tests would be much more complex. You can check out more testing examples from my other project here.



    写真でThisisEngineering RAEng 不平を言う

    最終語
    この記事では、私は、ケントC .ドッズが彼のブログ柱で提案した最高の実行に続きましたCommon mistakes with React Testing Library あなたが私のコードを見つけるかもしれない2020年5月に発表されますTest-Library Example 私はすぐにケントが医者をアップデートすると思いますが、2020年以降のテストをどのように書くべきかと思います.
    両方使用styled-component そして、このプロジェクトのインラインスタイルはUIの外観をより良くするようにするが、必要ではない.
    最後に、テストはフロントエンド開発の先進的なトピックです、私はそれのほんの少しの側面に触れるだけです、そして、私はまだ学んでいます.あなたが私を好きであるならば、ちょうど出発して、私はあなたがここの例またはあなたの個人的なプロジェクトでまわりで遊ぶ若干のものを使うことを提案します.したら、ファンダメンタルズをマスターすると、市場でより多くの選択肢を探索するためにあなたのニーズに最適なフィットを見つけるために起動することができます.

    ここでは、学習を継続することを推奨します.
  • Testing from Create React App
  • Which Query should I use From Testing Library
  • More examples from Testing Library
  • Write Test for Redux from Redux.js
  • Unit Test From Gatsby.js

  • Effective Snapshot Testing からKent C.Dodds .

  • リソースと記事Iは、この記事を終えました

  • で.

  • Don't useEffect as callback! で.

  • Common mistakes with React Testing Library そばKent C.Dodds .

  • Fix the not wrapped act warning そばKent C.Dodds .

  • Accessibility From React .

  • Axe for Jest .

  • 特別な感謝と彼のコースooloo.io .

    I have learned a lot in the past few months from both the course and fellows from the course - Martin Kruger and ProxN, who help inspire me a lot to finish this testing articles.

    Below are what I have learned

    • Creating pixel-perfect designs
    • Planning and implementing a complex UI component
    • Implement data fetching with error handling
    • Debugging inside an IDE
    • Writing integration tests
    • Professional Git workflow with pull requests
    • Code reviews
    • Continuous integration

    This is the Final finishing project as the outcome.