酵素を反応試験ライブラリーと比較する


Enzyme 長い間、反応アプリケーションをテストするための人気のあるライブラリでした.最近ではReact Testing Library 酵素の場所でトラクションを得ている.このポストでは、我々は2つの比較方法を見てみましょう.

概要
酵素は、簡単に主張し、操作し、あなたの反応成分の出力を横断する反応をJavaScriptテストユーティリティです.これはAirbnbによって作成され、2015年にリリースされました.酵素を使用するとき、あなたがテストしている反応成分をレンダリングして、それから、通過される特定の小道具または状態に基づく成分をテストするか、またはコンポーネントの中に含まれる機能を呼ぶことによって、一般的です.
酵素試験は通常、内部的に正しく動作する構成要素に焦点を当てているが、反応試験ライブラリは、ユーザが経験した反応反応器を試験することに重点を置いている.テストは、特定のコンポーネントまたは実装の状態ではなく、ユーザーの振る舞いを模倣した後にDOMの状態により集中する傾向があります.
より良い理解を得るために、いくつかのコードを見てみましょう.

設定
これらの2つのテストライブラリを比較するために、私は2つの別々のreposを作成しました.両方のプロジェクトには、同じ正確なアプリケーションが含まれています.唯一の違いは、1つのテストファイルは、酵素を使用して書かれている他の反応テストライブラリを使用して書かれています.あなたは簡単にアプリケーションを実行せずに、このポストに沿って従うことができますが、興味がある場合は、両方のreposはgithubで利用可能です.
Repo for testing with Enzyme
Repo for testing with React Testing Library
両ファイルにフォーカスするファイルはsrc/components/ToDo.test.js .
下記は酵素の典型的なスタイルで書かれたテストファイルです.
// testing-with-enzyme/src/components/ToDo.test.js

import React from "react"
import { mount } from "enzyme"
import ToDo from "./ToDo"

const setup = () => mount(<ToDo />)

describe("<ToDo/>", () => {
  describe("The default UI", () => {
    it("Renders two default todo items", () => {
      const app = setup()
      expect(app.find(".ToDoItem").length).toBe(2)
    })

    it("Has an input field", () => {
      const app = setup()
      expect(app.find(".ToDoInput").length).toEqual(1)
    })

    it("Has an add button", () => {
      const app = setup()
      expect(app.find(".ToDo-Add").length).toEqual(1)
    })
  })

  describe("Adding items", () => {
    window.alert = jest.fn()
    it("When the add button is pressed, if the input field is empty, prevent item from being added", () => {
      const app = setup()
      app.find(".ToDo-Add").simulate("click")
      expect(app.find(".ToDoItem").length).toBe(2)
    })

    it("When the add button is pressed, if the input field is empty, prevent item from being added", () => {
      const app = setup()
      app.find(".ToDo-Add").simulate("click")
      expect(window.alert).toHaveBeenCalled()
    })

    it("When the add button is pressed, if the input field has text, it creates a new todo item", () => {
      const app = setup()
      const event = { target: { value: "Create more tests" } }
      app.find("input").simulate("change", event)
      app.find(".ToDo-Add").simulate("click")
      expect(
        app
          .find(".ToDoItem-Text")
          .at(2)
          .text()
      ).toEqual("Create more tests")
    })
  })

  describe("Deleting items", () => {
    it("When the delete button is pressed for the first todo item, it removes the entire item", () => {
      const app = setup()
      app
        .find(".ToDoItem-Delete")
        .first()
        .simulate("click")
      expect(app.find(".ToDoItem").length).toBe(1)
    })
  })
})
そして、同じテスト、反応テストライブラリで書かれます.
// testing-with-react-testing-library/src/components/ToDo.test.js

import React from "react"
import { render } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import ToDo from "./ToDo"

const setup = () => render(<ToDo />)

describe("<ToDo/>", () => {
  describe("The default UI", () => {
    it("Renders two default todo items", () => {
      const { getAllByRole } = setup()
      expect(getAllByRole("listitem").length).toBe(2)
    })

    it("Has an input field", () => {
      const { getByRole } = setup()
      expect(getByRole("textbox")).toBeInTheDocument()
    })

    it("Has an add button", () => {
      const { getByLabelText } = setup()
      expect(getByLabelText("add")).toBeInTheDocument()
    })
  })

  describe("Adding items", () => {
    it("When the add button is pressed, if the input field is empty, prevent item from being added", () => {
      const { getByLabelText } = setup()
      window.alert = jest.fn()
      userEvent.click(getByLabelText("add"))
      expect(window.alert).toHaveBeenCalled()
    })

    it("When the add button is pressed, if the input field has text, it creates a new todo item", async () => {
      const { getByRole, getByLabelText, getByText } = setup()
      const toDoItem = "fake item"
      userEvent.type(getByRole("textbox"), toDoItem)
      userEvent.click(getByLabelText("add"))
      const item = await getByText(toDoItem)
      expect(item).toBeInTheDocument()
    })
  })

  describe("Deleting items", () => {
    it("When the delete button is pressed for the first todo item, it removes the entire item", async () => {
      const { getAllByRole, getByLabelText, queryByText } = setup()
      // default item
      const toDoItem = "clean the house"
      userEvent.click(getByLabelText(`delete ${toDoItem}`))
      const item = await queryByText(toDoItem)
      expect(item).toBeNull()
      // should only be 1 item left
      expect(getAllByRole("listitem").length).toBe(1)
    })
  })
})
両方のファイルは次のようにテストします.
  • つのデフォルトのToDo項目をレンダリングします
  • 入力フィールドを持つ
  • ボタンを追加する
  • 追加ボタンを押すと、入力フィールドが空の場合、項目が追加されないようにします
  • Addボタンを押すと、入力フィールドにテキストがある場合、新しいToDo項目を作成します
  • 最初のToDo項目のDeleteボタンを押すと、項目全体が削除されます
  • 酵素を使っているからmount 関数は、両方のテストのコンポーネントを同様にレンダリングし、コンポーネントのインスタンスを作成し、実際のDOMに添付します.別の人気の酵素機能を使用していた場合、これは本当ではないでしょう.shallow コンポーネントをレンダリングするにはこの投稿は、その違いに焦点を当てませんが、違いについての詳細を読むことができますhere .
    テストが異なるという最初の重要な方法は、DOMの特定の要素を検索してその存在またはその状態をアサートするときです.典型的には、酵素テストでは、以下のようにして要素名を検索します.
    it("Renders two default todo items", () => {
      const app = setup()
      expect(app.find(".ToDoItem").length).toBe(2)
    })
    
    反応テストライブラリを使用して同じテストを書くときは、代わりにgetAllByRole , そして、それを渡すARIA role of listitem .
    it("Renders two default todo items", () => {
      const { getAllByRole } = setup()
      expect(getAllByRole("listitem").length).toBe(2)
    })
    
    では、なぜ他のものよりも優れているのでしょうか?クラス名はむしろ任意ですが、アリアの役割はありません.アリア役割は、アクセシビリティ目的のために要素に追加文脈を提供します.将来的には、開発者として、クラス名を更新して更新することができます.我々は、名前を微調整することがあります、スタイルを変更することがあります、私たちは完全に私たちのCSSを書いた方法を変更することがあります.それが起こるならば、突然我々のテストは中断します.しかし、アプリケーションが壊れていません.クラス名ではなく、要素の役割に照会することによって、補助的な技術を持つユーザがアプリケーションを見ているかもしれないと同じように、要素を探すことでアプリケーションをテストしていることを保証することができます.我々は、彼らがユーザーに伝える目的に基づいて要素を探します.
    この概念を反応試験ライブラリdocsで論じた.Which query should I use? , これは、要素に対するクエリの優先順位を推奨します.例えば、私たちがその役割によって要素を見つけることができないなら、私たちの次の最良の賭けはラベルを探すことです.なぜ?さて、それは我々のユーザーがアプリケーションの特定の部分を見つけるために何をするでしょう.このハイライトReact Testing Library's guiding principles .

    The more your tests resemble the way your software is used, the more confidence they can give you.


    ライブラリは、あなたのウェブページがどのように使用されるかに密接に類似したテストを書くことを奨励する方法とユーティリティを提供するために書かれます.それは意図的にアクセシビリティに向けてユーザを駆動し、実装の詳細をテストすることから遠ざけます.
    別の例に移りましょう.そして、どのように我々のアプリケーションが正常にTO - DOリストに新しいアイテムを作成するかをテストする方法の違いを見てみましょう.
    酵素を使用すると、手動でDOMイベントを作成し、酵素simulate 関数は、change 我々が作成したこのイベントデータを持つイベント.以下はこの例です.
    // testing-with-enzyme/src/components/ToDo.test.js
    
    it("When the add button is pressed, if the input field has text, it creates a new todo item", () => {
      const app = setup()
      const event = { target: { value: "Create more tests" } }
      app.find("input").simulate("change", event)
      app.find(".ToDo-Add").simulate("click")
      expect(
        app
          .find(".ToDoItem-Text")
          .at(2)
          .text()
      ).toEqual("Create more tests")
    })
    
    これが我々が予想するものをする間、ユーザーがそれを使うのと同じ方法で、アプリケーションをテストしません.テストのために必要なAPIと実装情報がたくさんあります.我々は、イベントがどのように見えるかを知る必要があります.どのイベントAPIをシミュレートするかを知る必要があります.我々はクリックしたい要素のクラス名を知る必要があります.探している新しいリスト項目のクラス名を知る必要があります.最後に、テキストを比較することができるように、要素の順序を知る必要があります.これらのもののどれも、ユーザーが実際に知っているか、心配するものです.彼らが知っているすべては、ボックスに入力し、[追加]ボタンをクリックすると、新しい項目がリストに追加されます.
    我々のコード実装をテストして、アプリケーションが実際にどのように使用されるかについてテストに近づくのを避けるために、我々はもう一度、テストライブラリを反応させるために回されます.偽のDOMイベントオブジェクトを作成し、様々な変更イベントをシミュレートする代わりに、ユーザーが実際にアプリケーションと対話する方法を模倣する機能を持っていますuserEvent 'これはuser-event 図書館.

    user-event tries to simulate the real events that would happen in the browser as the user interacts with it. For example userEvent.click(checkbox) would change the state of the checkbox.


    これを使用して、反応テストライブラリで書かれたのと同じテストは以下のようになります.
    // testing-with-react-testing-library/src/components/ToDo.test.js
    
    it("When the add button is pressed, if the input field has text, it creates a new todo item", async () => {
      const { getByRole, getByLabelText, getByText } = setup()
      const toDoItem = "fake item"
      userEvent.type(getByRole("textbox"), toDoItem)
      userEvent.click(getByLabelText("add"))
      const item = await getByText(toDoItem)
      expect(item).toBeInTheDocument()
    })
    
    酵素テストと対照的に、反応テストライブラリテストを書くために、我々はユーザーが現在何をするかより多くを知る必要はありません.私たちは、最初の役割を持つ要素を探しますtextbox , 次に、使用してユーザーの入力をシミュレートしますuserEvent.type , 我々は、ユーザーがuserEvent.click アクセシビリティラベルの要素についてadd . それから、我々がタイプしたテキストが文書に現れると主張します.
    アプリケーションを使用してユーザーの経験のはるかに近い表現であることに加えて、このテストを書いてもこのようにはるかに少ない脆いテストを行います.我々は、クラス名を更新するか、リスト内の項目の数を変更することができますし、テストはまだアプリケーションが動作しているので、まだ通過します.酵素で書かれた最初のテストは同じことが言えません.

    ラッピング
    これらの例は、試験ライブラリの提供を反応させる利点と、酵素のより伝統的な試験ライブラリとはどのように異なるかを強調しようとするものである.すべてのテストライブラリを提供する反応は常にその指針の原則に戻ってくる.

    The more your tests resemble the way your software is used, the more confidence they can give you.


    コンポーネントへの小さな小さな変更が実際にどんな機能も壊さずにブレークするテストを引き起こす前に、我々はすべてそこにいました.反応テストライブラリは、適切に使用して、実装テストのこれらのタイプを記述するから、私たちを離れてガイドし、よりアクセス可能なコードとより密接にアプリケーションが使用される方法に似たより堅牢なテストを書くことに向かって.
    このポストは、反応テストライブラリに高レベルの導入として機能することを意図しているが、それは哲学で焼かれ、それだけですべてのライブラリの表面を傷を提供する必要があります.詳しくは、プロジェクトのサイトをご覧くださいtesting-library.com .
    あなたがこのポストを楽しんでいるか、役に立つとわかるならば、考えてください.
    あなたが新しいポストで更新されるままでいるならば、...
    何か質問、コメント、または単に挨拶をしたい場合.
    読書ありがとう!