무엇을 테스트할 것인가?
어떻게 테스트 할 것인가?
import { render, screen } from "@testing-library/react";
import App from "./App";
test("renders learn react link", () => {
render(<App />); // App을 렌더링
const linkElement = screen.getByText(/learn react/i); // 대소문자 상관없이 lear react가 있는지..
expect(linkElement).toBeInTheDocument();
});
테스팅 코드를 포함하는 파일
test 함수가 있다.
test(테스트에 대한 설명 , 함수)테스트 스크립트를 이용해서 미리 확인할 수 있다.
npm test
import Greeting from "./Greeting";
import { render, screen } from "@testing-library/react";
test("renders Hello wolrd as as text", () => {
// Arrange
render(<Greeting />); // 컴포넌트 엘리먼트 생성
// Act
// .. 여기선 없다.
// Assert
const helloWorldElement = screen.getByText("Hello world", { exact: true });
expect(helloWorldElement).toBeInTheDocument(); // expect 함수의 결과에 matcher 함수들이있음..
});
screen : 가상 DOM 또는 가상 화면에 엑세스할 수 있게 해주는 screen을 불러온다.get~ 함수가 에러를 발생시켜서 엘리먼터를 찾을 수 없다면 find~ 함수가 프로미스를 반환.import Greeting from "./Greeting";
import { render, screen } from "@testing-library/react";
describe("Greeting Component", () => {
test("renders Hello wolrd as as text", () => {
// Arrange
render(<Greeting />); // 컴포넌트 엘리먼트 생성
// Act
// .. 여기선 없다.
// Assert
const helloWorldElement = screen.getByText("Hello world", { exact: true });
expect(helloWorldElement).toBeInTheDocument(); // expect 함수의 결과에 matcher 함수들이있음..
});
});

import { useState } from "react";
const Greeting = () => {
const [changedText, setChangedText] = useState(false);
const changeTextHandler = () => {
setChangedText(true);
};
return (
<div>
<h2>Hello world</h2>
{!changedText && <p>It's nice to meet you</p>}
{changedText && <p>Changed!</p>}
<button onClick={changeTextHandler}>Change Text</button>
</div>
);
};
export default Greeting;
import Greeting from "./Greeting";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
describe("Greeting Component", () => {
test("renders Hello world as an text", () => {
// Arrange
render(<Greeting />); // 컴포넌트 엘리먼트 생성
// Act
// .. 여기선 없다.
// Assert
const helloWorldElement = screen.getByText("Hello world", { exact: true });
expect(helloWorldElement).toBeInTheDocument(); // expect 함수의 결과에 matcher 함수들이있음..
});
// 버튼을 클릭하지 않았을 때 nice to meet you가 렌더링 되는지
test("renders nice to meet you if the button was NOT clicked", () => {
render(<Greeting />);
const outputElement = screen.getByText("nice to meet you", {
exact: false,
});
expect(outputElement).toBeInTheDocument();
});
// 버튼을 클릭했을 때 Changed!가 렌더링 되는지
test("renders Changed! if the button was clicked", () => {
// Arrange
render(<Greeting />);
// Act
const buttonElement = screen.getByRole("button");
userEvent.click(buttonElement);
// Assert
const outputElement = screen.getByText("Changed!");
expect(outputElement).toBeInTheDocument();
});
});
userEvent는 실제 화면에서 사용자 이벤트를 작동시키도록 돕는 객체
import { useState } from "react";
const Greeting = () => {
const [changedText, setChangedText] = useState(false);
const changeTextHandler = () => {
setChangedText(true);
};
return (
<div>
<h2>Hello world</h2>
<p>It's nice to meet you</p>
{changedText && <p>Changed!</p>}
<button onClick={changeTextHandler}>Change Text</button>
</div>
);
};
export default Greeting;
!changedText일때 nice to meet you가 렌더링 되도록 해야하는데 해당 조건을 넣는 것을 까먹어도 테스트는 통과가 된다..!import Greeting from "./Greeting";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
describe("Greeting Component", () => {
test("renders Hello world as an text", () => {
// Arrange
render(<Greeting />); // 컴포넌트 엘리먼트 생성
// Act
// .. 여기선 없다.
// Assert
const helloWorldElement = screen.getByText("Hello world", { exact: true });
expect(helloWorldElement).toBeInTheDocument(); // expect 함수의 결과에 matcher 함수들이있음..
});
test("renders nice to meet you if the button was NOT clicked", () => {
render(<Greeting />);
const outputElement = screen.getByText("nice to meet you", {
exact: false,
});
expect(outputElement).toBeInTheDocument();
});
test("renders Changed! if the button was clicked", () => {
// Arrange
render(<Greeting />);
// Act
const buttonElement = screen.getByRole("button");
userEvent.click(buttonElement);
// Assert
const outputElement = screen.getByText("Changed!");
expect(outputElement).toBeInTheDocument();
});
// 버튼을 클릭했을 때 nice to meet you가 보이지 않는지 테스트
test("NOT renders nice to meet you if the button was NOT clicked", () => {
render(<Greeting />);
const buttonElement = screen.getByRole("button");
userEvent.click(buttonElement);
const outputElement = screen.queryByText("nice to meet you");
// expect(outputElement).not.toBeInTheDocument();
expect(outputElement).toBeNull(); //도 가능
});
});
expect(outputElement).not.toBeInTheDocument() 혹은 expect(outputElement).toBeNull();을 이용해서 확인.
const Output = (props) => {
return <p>{props.children}</p>;
};
export default Output;
import { useState } from "react";
import Output from "./Output";
const Greeting = () => {
const [changedText, setChangedText] = useState(false);
const changeTextHandler = () => {
setChangedText(true);
};
return (
<div>
<h2>Hello world</h2>
{!changedText && <Output>It's nice to meet you</Output>}
{changedText && <Output>Changed!</Output>}
<button onClick={changeTextHandler}>Change Text</button>
</div>
);
};
export default Greeting;
import Greeting from "./Greeting";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
describe("Greeting Component", () => {
test("renders Hello world as an text", () => {
// Arrange
render(<Greeting />); // 컴포넌트 엘리먼트 생성
// Act
// .. 여기선 없다.
// Assert
const helloWorldElement = screen.getByText("Hello world", { exact: true });
expect(helloWorldElement).toBeInTheDocument(); // expect 함수의 결과에 matcher 함수들이있음..
});
test("renders nice to meet you if the button was NOT clicked", () => {
render(<Greeting />);
const outputElement = screen.getByText("nice to meet you", {
exact: false,
});
expect(outputElement).toBeInTheDocument();
});
test("renders Changed! if the button was clicked", () => {
// Arrange
render(<Greeting />);
// Act
const buttonElement = screen.getByRole("button");
userEvent.click(buttonElement);
// Assert
const outputElement = screen.getByText("Changed!");
expect(outputElement).toBeInTheDocument();
});
// 버튼을 클릭했을 때 nice to meet you가 보이지 않는지 테스트
test("NOT renders nice to meet you if the button was NOT clicked", () => {
render(<Greeting />);
const buttonElement = screen.getByRole("button");
userEvent.click(buttonElement);
const outputElement = screen.queryByText("nice to meet you");
// expect(outputElement).not.toBeInTheDocument();
expect(outputElement).toBeNull(); //도 가능
});
});
render(<Greeting />) : 해당 컴포넌트에서 요구되는 컴포넌트 트리 전체를 렌더링하고 있음.
Output이 더 복잡해진다면 테스트를 분리하는 것이 좋지만 여기서는 굳이 분리할 필요가 없다.
import { useEffect, useState } from "react";
const Async = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.json())
.then((data) => {
setPosts(data);
});
}, []);
return (
<div>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default Async;
import { render, screen } from "@testing-library/react";
import Async from "./Async";
describe("Async Component", () => {
test("renders posts if request succeeds", async () => {
render(<Async />);
const listItemElements = await screen.findAllByRole("listitem", {}, {});
expect(listItemElements).not.toHaveLength(0); // 빈 배열인지 아닌지 확인.
});
});
처음엔 getAllByRole을 이용해서 리스트 아이템을 찾고, 빈 배열인지 아닌지 확인하려 했다.
그러나 Async 코드는 비동기 코드이므로 가장 초기에는 빈 배열로 세팅되어있다.
getAllByRole을 사용하면screen의 아이템들을 즉시 가져온다. 따라서 프로미스를 사용하는findAllByRole이나find를 이용한다.
findAllByRole / find을 사용하면 프로미스를 반환한다. → 스크린을 여러 차례 실행.
findAllByRole('요소', {exact.. }, {timeout...}) : 기본 타임아웃은 1초async / await을 사용한다.
HTTP 요청을 보내므로 위와 같은 테스트는 좋은 방법이 아니다..!
테스트를 작성할 때 진짜 요청을 전송하지 않거나 일종의 테스팅 서버로 요청을 전송한다.
테스트를 작성할 때는 내가 작성하지 않는 코드에 대해서 테스트를 작성하면 안된다. 그러므로 fetch가 요청을 전송하는지를 테스트하면 안된다!
즉, 응답 데이터를 받았을 때 내가 작성한 컴포넌트가 올바르게 작동하는지를 테스트 해야한다.
fetch함수를mock함수로 대체한다. 이는 내장 함수를 덮어쓰는 더미 함수를 사용하는 것으로 개발자가 원하는 바를 수행하면서도 진짜 요청을 전송하지 않는 더미 함수를 사용한다.
mock을 사용하여 테스트한다.import { render, screen } from "@testing-library/react";
import Async from "./Async";
describe("Async Component", () => {
test("renders posts if request succeeds", async () => {
// Arrange
window.fetch = jest.fn(); // mock 함수를 만듦. => fetch를 더미함수로 덮어씀.
window.fetch.mockResolvedValueOnce({
json: async () => [{ id: "p1", title: "First Post" }], // response.json()을 사용했으니까 여기서도 json..
}); // fetch 함수가 호출되었을 때 결정되어야하는 값을 설정할 수 있게 한다.
render(<Async />);
const listItemElements = await screen.findAllByRole("listitem", {}, {});
expect(listItemElements).not.toHaveLength(0);
});
});
fetch 함수를 모의 함수로 대체할 수 있게 된다.