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 |