개발자 친화적인 React 단위 테스트 작성

24242 단어 webdevreacttesting
사람이 읽을 수 있는 방식으로 React 단위(구성 요소) 테스트를 작성하려고 합니다. 오늘날의 모범 사례와 함께 귀하의 삶(및 동료 개발자의 삶)을 더 쉽게 만들고 프로덕션 버그의 수를 줄여야 합니다.

it("should render a button with text", () => {
  expect(
    <Button>I am a button</Button>,
    "when mounted",
    "to have text",
    "I am a button"
  );
});


이 구성 요소 테스트는 실제입니다. 곧 다시 시작합니다... [🔖]


문제



스냅샷 및 직접 DOM 비교는 깨지기 쉬운 반면 체인 구문과 같은 JQuery는 잘못 읽고 테스트를 장황하게 만듭니다. 이 경우 가독성 문제를 해결하는 방법은 무엇입니까? 구현 세부 정보를 숨겨 유지하는 방법testing components' data flow in isolation은 무엇입니까?

다음은 React에서 단위 테스트에 대한 나의 접근 방식입니다. 그것은 약간의 명확성을 가지고 합의를 따르는 것을 목표로 합니다.

구성 요소(A 버튼)



이 데모에는 trivialMaterial UI-like button이 사용됩니다. 몇 가지 테스트 예제의 도움으로 개념을 전개하는 것은 충분히 간단합니다.



// Button.js

export default function Button({
  children,
  disabled = false,
  color,
  size,
  onClick,
}) {
  const handleClick = () => {
    if (!disabled) {
      if (typeof onClick === "function") {
        onClick();
      }
    }
  };

  return (
    <button
      className={classNames("Button", {
        [color]: color,
        [size]: size,
      })}
      disabled={disabled}
      onClick={handleClick}
    >
      {children}
    </button>
  );
}



테스트 라이브러리



맨 위에 있는 [🔖] 테스트 케이스로 돌아가기. 모든 테스트 프레임워크와 호환되는 어설션 라이브러리인 UnexpectedJS을 사용하고 몇 가지 플러그인으로 보완하여 React 구성 요소 및 DOM 작업을 돕습니다.

Jest는 테스트 러너이며 뒤에서는 react-domreact-dom/test-utils을 종속성으로 가지고 있습니다.

테스트 설정



참조로 사용할 수 있는 example GitHub repo이 있습니다. 전체 사진을 보려면 거기로 가십시오.

그렇지 않으면 여기에 더 흥미로운 순간이 있습니다.

프로젝트 구조




-- src
    |-- components
    |   |-- Button
    |   |   |-- Button.js
    |   |   |-- Button.scss
    |   |   |-- Button.test.js
    |-- test-utils
    |   |-- unexpected-react.js




테스트 플러그인


package.json
"devDependencies": {
  ...
+ "sinon": "9.2.4",
+ "unexpected": "12.0.0",
+ "unexpected-dom": "5.0.0",
+ "unexpected-reaction": "3.0.0",
+ "unexpected-sinon": "11.0.1"
}

Sinon는 함수(UI와의 특정 사용자 상호 작용의 결과로 실행되는 콜백 구성 요소 소품)를 감시하는 데 사용됩니다.

테스트 도우미


unexpected-react.js라는 테스트 도우미의 구조는 다음과 같습니다.

// unexpected-react.js

import unexpected from "unexpected";
import unexpectedDom from "unexpected-dom";
import unexpectedReaction from "unexpected-reaction";
import unexpectedSinon from "unexpected-sinon";

const expect = unexpected
  .clone()
  .use(unexpectedDom)
  .use(unexpectedReaction)
  .use(unexpectedSinon);

export { simulate, mount } from "react-dom-testing";

export default expect;


단순히 Button의 테스트를 결합하는 데 필요한 모든 기능을 내보냅니다.

버튼 구성 요소 테스트




// Button.test.js

import expect, { mount, simulate } from "../../test-utils/unexpected-react";
import React from "react";
import sinon from "sinon";

import Button from "./Button";

describe("Button", () => {
  // Test cases
});


개별 단위/구성 요소 테스트는 describe() 블록 내에 배치됩니다. 아래를 참조하십시오.

1. 텍스트로 렌더링합니다.





it("should render with text", () => {
  expect(
    <Button>I am a button</Button>,
    "when mounted",
    "to have text",
    "I am a button"
  );
});


버튼이 지정된 텍스트로 렌더링되는지 확인합니다.

2. 사용자 지정 마크업으로 렌더링합니다.





it("should render with markup", () => {
  expect(
    <Button>
      <span>Download</span>
      <span>⬇️</span>
    </Button>,
    "when mounted",
    "to satisfy",
    <button>
      <span>Download</span>
      <span>⬇️</span>
    </button>
  );
});


DOM 구조를 비교하려는 경우(이 경우 의미가 있을 수 있음) 이것이 갈 길입니다.

관련 어설션과 함께 data-test-id를 사용할 수도 있습니다. FX.

it("should render with markup", () => {
  expect(
    <Button>
      <span>
        <i />
        <span data-test-id="button-text">
          Download
        </span>
      </span>
    </Button>,
    "when mounted",
    "queried for test id"
    "to have text",
    "Download"
  );
});



3. 기본 버튼을 렌더링합니다.





it("should render as primary", () => {
  expect(
    <Button color="primary">Primary</Button>,
    "when mounted",
    "to have class",
    "primary"
  );
});


기본 및 보조의 두 가지 prop 값color이 지원됩니다. 그런 다음 CSS 클래스로 설정됩니다.

4. 작은 버튼을 렌더링합니다.





it("should render as small", () => {
  expect(
    <Button size="small">Small</Button>,
    "when mounted",
    "to have class",
    "small"
  );
});

color와 유사하게 size 소품에는 small과 large의 두 가지 값이 있습니다.

5. 비활성화로 렌더링합니다.





it("should render as disabled", () => {
  expect(
    <Button disabled>Disabled</Button>,
    "when mounted",
    "to have attributes",
    {
      disabled: true,
    }
  );
});

disabled 속성을 확인합니다. 그게 다야.

6. 클릭 핸들러를 트리거하지 마십시오.




it("should NOT trigger click if disabled", () => {
  const handleClick = sinon.stub();

  const component = mount(
    <Button onClick={handleClick} disabled>Press</Button>
  );

  simulate(component, { type: "click" });

  expect(handleClick, "was not called");
});

onClick 콜백은 비활성화된 버튼에서 실행되지 않아야 합니다.

7. 클릭을 처리합니다.




it("should trigger click", () => {
  const handleClick = sinon.stub();

  const component = mount(
    <Button onClick={handleClick}>Click here</Button>
  );

  simulate(component, { type: "click" });

  expect(handleClick, "was called");
});


핸들러에 전달된 인수를 테스트해야 하는 경우 여기에 있는 was called 어설션이 더 나은 대안입니다. FX.

// Passing a checkbox state (checked) to the callback
expect(handleClick, "to have a call satisfying", [true]);



8. 출력을 테스트합니다.





모든 단위 테스트를 통과하면 터미널에 표시되는 내용입니다.

마지막 말



React Testing Library에 의해 시작되었으며 the majority of our community is going after it . 그 지도 원칙은 다음과 같습니다.

...you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended.



이 문장은 "구성 요소의 DOM 구조에 대해 테스트하지 말고 데이터 흐름에 집중해야 합니다."와 같은 의미로 해석됩니다. 구성 요소 DOM의 변경으로 인해 테스트가 중단되지 않아야 합니다.

UnexpectedJS는 data-test-id(또는 aria-* 속성)을 쉽게 테스트할 수 있도록 함으로써 이 원칙을 준수하는 동시에 사용자 지정 어설션으로 사람이 읽을 수 있는 단위 테스트 작성을 권장합니다.

추신: 그러한 접근 방식이 귀하에게 가치가 있습니까?


내 프로그래밍 뉴스레터 가입



한 달에 한 번 발송합니다. 여기에는 Google 검색만으로는 쉽게 답변을 찾을 수 없는 주제에 대한 유용한 링크와 생각과 최신 글이 포함되어 있습니다.

재미있을 것 같으면 head over and add your email .
스팸이 없습니다.

좋은 웹페이지 즐겨찾기