React
20210611 React11 : React Component Test, Testing-library/react, component testing 예제, Refactoring(Function Clear), jest-dom, user-event
React 10
React Component Test
- 기본적으로 개발을 할때 testing 코드를 먼저 작성하고 개발 코드를 작성
Testing-library/react
- 기존의 jest testing 함수 말고, 더 다양하게 testing을 하기 위한 함수를 제공하는 라이브러리
- CRA 프로젝트인 경우 자체적으로 설치 되어 있음
- @testing-library/react
testing 작성 예시
- 구현할 요소와 기능
- 컴포넌트가 정상적으로 생성된다.
- "button" 이라고 쓰여진 엘리먼트는 HTMLButtonElement 이다.
- 버튼을 클릭하면, p 태그 안에 "버튼이 방금 눌렸다"라고 쓰여진다.
- 버튼을 클릭하기 전에는, p 태그 안에 "버튼이 눌리지 않았다"라고 쓰여진다.
- 버튼 클릭 5초 후에는, p 태그 안에 "버튼이 눌리지 않았다"라고 쓰여진다.
- 버튼을 클릭하면, 5초 동안 버튼이 비활성화 된다.
TEST code 작성
1. 컴포넌트가 정상적으로 생성된다.
render()
: testing-library/react에서 제공하는 함수로, 컴포넌트가 쓰여지는 경우 제대로 표시 되는지 test 하는 코드로 해당 컴포넌트 표현을 넣고 변수로 받아서 null인지 test
import { render } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it("컴포넌트가 정상적으로 생성된다.", () => {
const button = render(<Button />);
expect(button).not.toBe(null);
});
});
// Button.jsx
export default function Button() {
return <></>;
}
2. "button" 이라고 쓰여진 엘리먼트는 HTMLButtonElement 이다.
- testing-library가 제공하는 render함수에서
getByText
라는 함수를 통해서 render안의 해당 컴포넌트를 구성하고 있는 요소들중 getByText 인자로 들어오는 string으로 내부를 구성하는 element를 가져옴 - react에서 제공하는 Element 중 Button 객체와 일치하는지 확인 (
HTMLButtonElement
)
import { render } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it('"button" 이라고 쓰여진 엘리먼트는 HTMLButtonElement 이다.', () => {
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
// button이라는 text가 들어있는 element를 구해옴
expect(buttonElement).toBeInstanceOf(HTMLButtonElement);
});
});
// Button.jsx
export default function Button() {
return <button>button</button>;
}
3. 버튼을 클릭하면, p 태그 안에 "버튼이 방금 눌렸다."라고 쓰여진다.
- 해당 버튼을 가져오고
fireEvent
객체를 통해서 특정 event 종류 함수의 인자로 해당 버튼 요소를 넣으면 해당 이벤트가 반응 - 눌린 후 '버튼이 방금 눌렸다' 라는 내용의 element를 가져와서 해당 element가 있는지 null test
- 그리고 그게 p태그 요소인지 test
import { render, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it('버튼을 클릭하면, p 태그 안에 "버튼이 방금 눌렸다."라고 쓰여진다.', () => {
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
const p = getByText("버튼이 방금 눌렸다.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
});
// Button.jsx
export default function Button() {
return (
<div>
<button>button</button>
<p>버튼이 방금 눌렸다.</p>
</div>
);
}
4. 버튼을 클릭하기 전에는, p 태그 안에 "버튼이 눌리지 않았다."라고 쓰여진다.
- 똑같이 위와 같이 하되, click event 없이 test 코드 작성
import { render, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it('버튼을 클릭하기 전에는, p 태그 안에 "버튼이 눌리지 않았다."라고 쓰여진다.', () => {
const { getByText } = render(<Button />);
const p = getByText("버튼이 눌리지 않았다.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
});
// Button.jsx
import { useState } from "react";
export default function Button() {
const [message, setMessage] = useState("버튼이 눌리지 않았다.");
return (
<div>
<button onClick={click}>button</button>
<p>{message}</p>
</div>
);
function click() {
setMessage("버튼이 방금 눌렸다.");
}
}
5. 버튼 클릭 5초 후에는, p 태그 안에 "버튼이 눌리지 않았다"라고 쓰여진다.
- 5초 시간의 경과를 만들어 test 해야함
jest.useFakeTimers()
: 진짜 시간의 경과를 기다리기 보다는, 프로그램의 시간만 돌려서 test 함jest.advanceTimersByTime(시간)
- AAA (Arrange Act Assert, test 방법론 중의 하나 임) : state가 변화가 있는 test를 하는 경우
act()
라는 함수를 사용해서 해당 함수를 넣고 state가 변화하는 행위가 지나가도록 해야함- 여기서 시간이 흐르는 것도 state가 변하기 때문에 act를 사용함
import { act, render, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it('버튼 클릭 5초 후에는, p 태그 안에 "버튼이 눌리지 않았다"라고 쓰여진다.', () => {
jest.useFakeTimers();
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
// 5초 흐른다.
// 시간이 흐르거나, 버튼을 클릭하거나, state가 변하거나 모두 act를 사용해야 함 (AAA 패턴, Arrange Act Assert)
act(() => {
jest.advanceTimersByTime(5000);
});
const p = getByText("버튼이 눌리지 않았다.");
expect(p).not.toBeNull();
expect(p).toBeInstanceOf(HTMLParagraphElement);
});
});
Refactoring (리팩토링) : Function Clear, 값-변수 관리
- Handler 제거 해주기 (clear)
- setTimeout이 돌고 있는 중에(즉, 함수가 처리하고 있는 중에), 요소가 unmount 되어 없어지는 경우에도 계속 함수를 처리할 수 있는 일이 벌어질 수 있기 때문에 해당 함수를 clear 해야 함
useEffec
t의 return 함수 로 해당 handler를 clear 함setTimeout()
메서드는 일정 시간(밀리초 단위)이 흐른 후에 실행할 함수를 지정한다.setTimeout()의 반환값
을clearTimeout()
메서드의 인자로 사용하면 계획된 함수의 실행을 취소 가능함- 반환값은
useRef
를 통해서 참조할 수 있도록 함
- 표시하는 string 내용 자체를 넣기 보단, TEXT 값으로 객체형태로 변수로 보관하여 사용함
import { useEffect, useRef, useState } from "react";
const BUTTON_TEXT = {
NORMAL: "버튼이 눌리지 않았다.",
CLICKED: "버튼이 방금 눌렸다.",
};
export default function Button() {
const [message, setMessage] = useState(BUTTON_TEXT.NORMAL);
const timer = useRef();
useEffect(() => {
return () => {
if (timer.current) {
clearTimeout(timer.current);
}
};
}, []);
return (
<div>
<button onClick={click}>button</button>
<p>{message}</p>
</div>
);
function click() {
setMessage(BUTTON_TEXT.CLICKED);
timer.current = setTimeout(() => {
setMessage(BUTTON_TEXT.NORMAL);
}, 5000);
}
}
6. 버튼을 클릭하면, 5초 동안 버튼이 비활성화 된다.
- disabled 속성을 제어하면 됨
- 클릭해서 시간이 지난후, 전의 disabled의 true false를 체크함
import { act, render, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button 컴포넌트 (@testing-library/react)", () => {
it("버튼을 클릭하면, 5초 동안 버튼이 비활성화 된다.", () => {
jest.useFakeTimers();
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
// 비활성화
expect(buttonElement.disabled).toBeTruthy();
act(() => {
jest.advanceTimersByTime(5000);
});
// 활성화
expect(buttonElement.disabled).toBeFalsy();
});
});
import { useEffect, useRef, useState } from "react";
const BUTTON_TEXT = {
NORMAL: "버튼이 눌리지 않았다.",
CLICKED: "버튼이 방금 눌렸다.",
};
export default function Button() {
const [message, setMessage] = useState(BUTTON_TEXT.NORMAL);
const timer = useRef();
useEffect(() => {
return () => {
if (timer.current) {
clearTimeout(timer.current);
}
};
}, []);
return (
<div>
{/*disabled 속성을 text로 제어함*/}
<button onClick={click} disabled={message === BUTTON_TEXT.CLICKED}>
button
</button>
<p>{message}</p>
</div>
);
function click() {
setMessage(BUTTON_TEXT.CLICKED);
timer.current = setTimeout(() => {
setMessage(BUTTON_TEXT.NORMAL);
}, 5000);
}
}
testing-library 그 외
jest dom
- gitHub : testing-library/jest-dom
- 여러 상황의 testing 코드를 조금 더 보기 쉽게 testing 함수로 구현 해놓음으로 써 testing 코드에 사용할 수 있게 해줌
it("버튼을 클릭하면, 5초 동안 버튼이 비활성화 된다.", () => {
jest.useFakeTimers();
const { getByText } = render(<Button />);
const buttonElement = getByText("button");
fireEvent.click(buttonElement);
// 비활성화
expect(buttonElement.disabled).toBeDisabled();
act(() => {
jest.advanceTimersByTime(5000);
});
// 활성화
expect(buttonElement.disabled).not.toBeDisabled();
});
user-event
- testing-library/user-event
- user가 event를 날릴때 그것을 test 할 수 있음 (fireEvent 말고)