2차 공부/TIL

24.08.22 react-test 공부 2 / toHaveStyle, toBeDisabled, getByRole, userEvent

공대탈출 2024. 8. 22. 12:05
test("on/off button has blue color", () => {
    render(<App />);
    const buttonElement = screen.getByTestId("onOffButton");
    expect(buttonElement).toHaveStyle({ backgroundColor: "blue" });
});

어떤 컴포넌트에 어떤 스타일이 들어있는지에 대한 검사도 가능하다.

toHaveStyle안에 객체 형식으로 여러 스타일을 검사할 수도 있으며, 백틱형태로도 검사할 수 있다.

expect(button).toHaveStyle('display: none')
expect(button).toHaveStyle({display: 'none'})
expect(button).toHaveStyle(`
  background-color: red;
  display: none;
`)

 

버튼을 disable하는 기능에 대한 검사도 가능하다. HTML의 disabled속성을 이용하면 된다.

test("Prevent the -,+ button from being pressed when the on/off button is clicked", () => {
    render(<App />);
    const onOffButton = screen.getByTestId("onOffButton");
    fireEvent.click(onOffButton);

    const plusButtonElement = screen.getByTestId("plusButton");
    const minusButtonElement = screen.getByTestId("minusButton");
    expect(plusButtonElement).toBeDisabled();
    expect(minusButtonElement).toBeDisabled();
});

 

const App = () => {
    const [counter, setCounter] = useState(0);
    const [disabled, setDisabled] = useState(false);

    const wrapper = {
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
    };

    return (
        <div style={wrapper}>
            <h3 data-testid="counter">{counter}</h3>
            <div>
                <button data-testid="minusButton" onClick={() => setCounter((count) => count - 1)} disabled={disabled}>
                    -
                </button>
                <button data-testid="plusButton" onClick={() => setCounter((count) => count + 1)} disabled={disabled}>
                    +
                </button>
            </div>
            <div>
                <button
                    data-testid="onOffButton"
                    style={{ backgroundColor: "blue" }}
                    onClick={() => setDisabled((prev) => !prev)}
                >
                    on/off
                </button>
            </div>
        </div>
    );
};

이렇게 disable을 담을 state를 만들어주고, 버튼 클릭시 state를 반대로 바꿔 disabled가 적용된 요소에 state를 넣어 해당 속성이 작동하도록 만들어 주면 된다.

 


여태까지 요소를 가져올 때 data-testid를 요소에 넣어주고, 해당 id를 getByTestId로 지정해 가져왔다.

하지만 Testing-library에서 쿼리 우선순위를 제시하고있다.

여태까지 사용했던 getByTestId는 유저가 볼수없는 영역이라 role이나 동적으로 제공되는 text라서 matching할 수 없을 때 사용해야 하는 최하위 우선순위이다.

따라서 우리는 모두가 접근가능한 쿼리를 사용하고자 위 쿼리들을 우선적으로 사용하려 해야한다.

그 중 getByRole을 자주 사용하는데, 요소의 역할이 무엇인지, name이 어떤것으로 지정되어있는지 체크하는 것이다.

name: /submit/i에서 i는 name에 지정된 값의 대소문자를 구별하지않고 처리해준다는 의미이다.

name='Submit'과 name='submit'을 동일하게 봐준다는 것이다.

 

import { fireEvent, render, screen } from "@testing-library/react";
import App from "./App";

test("the counter starts at 0", () => {
    render(<App />);
    //screen object를 이용해서 원하는 엘리먼트에 접근(ID)
    const counterElement = screen.getByRole("heading", { name: "0" });
    //id가 counter인 엘리먼트의 텍스트가 0인지 테스트
    expect(counterElement).toHaveTextContent(0);
});

test("minus button has correct text", () => {
    render(<App />);
    const minusButtonElement = screen.getByRole("button", { name: "-" });
    expect(minusButtonElement).toHaveTextContent("-");
});

test("plus button has correct text", () => {
    render(<App />);
    const plusButtonElement = screen.getByRole("button", { name: "+" });
    expect(plusButtonElement).toHaveTextContent("+");
});

test("when the + button is pressed, the counter changes to 1", () => {
    render(<App />);

    const plusButtonElement = screen.getByRole("button", { name: "+" });
    //plusButton을 id로 가진 버튼에 클릭이벤트를 작동한 상태
    fireEvent.click(plusButtonElement);

    const counterElement = screen.getByRole("heading", { name: "1" });
    //클릭 이벤트 후에 counterElement가 1을 가지고 있는지 확인
    expect(counterElement).toHaveTextContent(1);
});

test("when the - button is pressed, the counter changes to -1", () => {
    render(<App />);

    const minusButtonElement = screen.getByRole("button", { name: "-" });
    //minusButton을 id로 가진 버튼에 클릭이벤트를 작동한 상태
    fireEvent.click(minusButtonElement);

    const counterElement = screen.getByRole("heading", { name: "-1" });
    //클릭 이벤트 후에 counterElement가 -1을 가지고 있는지 확인
    expect(counterElement).toHaveTextContent(-1);
});

test("on/off button has blue color", () => {
    render(<App />);
    const buttonElement = screen.getByRole("button", { name: "on/off" });
    expect(buttonElement).toHaveStyle({ backgroundColor: "blue" });
});

test("Prevent the -,+ button from being pressed when the on/off button is clicked", () => {
    render(<App />);
    const onOffButton = screen.getByRole("button", { name: "on/off" });
    fireEvent.click(onOffButton);

    const plusButtonElement = screen.getByRole("button", { name: "+" });
    const minusButtonElement = screen.getByRole("button", { name: "-" });
    expect(plusButtonElement).toBeDisabled();
    expect(minusButtonElement).toBeDisabled();
});

다만 클릭이벤트와 연관되어있는 counter을 검사하는 로직은 동적으로 텍스트를 변경하므로 getByTestId가 더 나은 것으로 생각한다.

 

getByTestId보다 getByRole이 더 유저사용과 비슷한 테스트 방법이지만, 테스트에 소요되는 시간이 더 오래걸린다.

하지만 테스트를 한다는 것 자체가 효율과는 먼 방법이므로, 효율을 따지기보단, 테스트의 본질인 유저사용과 최대한 비슷한 상태에서 테스트를 진행하는 getByRole이 더욱 적절하다.


fireEvent보다는 userEvent를 사용하자

버튼 이벤트의 테스트에서 fireEvent의 click을 사용하여 이벤트 처리를 했다. 하지만 userEvent API를 사용하는게 더 좋은 방법이다.

userEvent는 fireEvent를 사용해서 만들어졌는데, 엘리먼트 타입에 따라 label을 클릭했을때, checkbox/radio를 클릭했을 때 그 타입에 맞는 더욱 적절한 반응을 보여준다.

function click(element, init, {skipHover = false, clickCount = 0} = {}) {
  if (!skipHover) hover(element, init)
  switch (element.tagName) {
    case 'LABEL':
      clickLabel(element, init, {clickCount})
      break
    case 'INPUT':
      if (element.type === 'checkbox' || element.type === 'radio') {
        clickBooleanElement(element, init, {clickCount})
      } else {
        clickElement(element, init, {clickCount})
      }
      break
    default:
      clickElement(element, init, {clickCount})
  }
}

 

예를들어 fireEvent로 버튼을 클릭하면 버튼이 focus되지 않지만, userEvent로 클릭하면 해당 버튼이 focus된다. 위에서 getByRole과 getByTestId에서 말했던 것처럼 테스트는 유저가 실제 사용하는 것과 동일하게 표현하는 것이 올바른 테스트이므로 userEvent가 fireEvent보다 더 추천되는 방법이다.

'2차 공부 > TIL' 카테고리의 다른 글

24.08.22 Next.js Layout / metadata  (0) 2024.08.22
24.08.22 yarn vite js react 에 eslint적용하기  (0) 2024.08.22
24.08.21 TDD체험해보기  (0) 2024.08.21
24.08.21 react-test 공부1  (0) 2024.08.21
24.08.20 개인과제 회고  (0) 2024.08.20