539.첫 번째 테스트 실행하기
프로젝트 설정
- 프로젝트는 create-react-app을 사용해 생성된 일반 리액트 프로젝트입니다.
- index.js 파일을 약간 정리했으며, App.test.js와 setupTests.js 파일은 그대로 두었습니다.
- setupTests.js 파일은 테스팅 설정을 위한 파일로, 특별히 수정할 필요는 없습니다.
테스팅 파일 설명
- App.test.js 파일은 테스트 코드를 포함하고 있습니다. 테스트 파일의 이름은 일반적으로 컴포넌트 파일명에 .test.js를 붙이는 형식입니다.
- App.test.js 파일에는 test 함수가 있으며, 두 개의 인자를 가집니다:
- 테스트에 대한 설명 (첫 번째 인자)
- 익명 함수로 실제 테스트 코드를 포함 (두 번째 인자)
테스트 코드 실행
- App 컴포넌트를 렌더링하고, learn react 텍스트가 문서에 있는지를 확인하는 테스트를 실행합니다.
- 정규 표현식을 사용해 대소문자 구분 없이 텍스트를 찾고, toBeInTheDocument 테스트로 해당 요소의 존재를 확인합니다.
- App.js 파일의 <a> 태그 안에 Learn React 텍스트가 있기 때문에 이 테스트는 통과합니다.
테스트 실행 방법
- package.json 파일에 있는 테스트 스크립트를 사용합니다. npm test 명령어로 자동화된 테스트를 실행할 수 있습니다.
- 테스트 실행 후, 테스트 결과를 터미널에서 확인할 수 있습니다. 모든 테스트가 통과했는지, 실패했는지에 대한 정보를 제공합니다.
- 파일 변경 시 자동으로 테스트가 재실행되며, 테스트가 실패할 경우 원인을 터미널에서 확인할 수 있습니다.
실습 예제
- App.js 파일의 Learn React 텍스트를 Learn more로 변경하면, 테스트가 실패하는 것을 볼 수 있습니다.
- 실패 원인과 함께, 실패한 부분의 코드를 터미널에서 확인할 수 있습니다.
- 테스트를 통과시키기 위해 코드를 수정하거나 테스트 자체를 조정할 수 있습니다.
테스트 종료
- 테스트 모드를 종료하려면 Ctrl + C 키를 눌러 파일 감시를 중지할 수 있습니다. 다시 테스트하려면 npm test 명령어를 실행합니다.
이제 기본적인 자동화된 테스팅 개념과 작동 방식을 이해했으니, 더 깊이 있는 내용을 살펴보겠습니다.
540.첫 번째 테스트 작성하기
이번에는 새 컴포넌트를 생성하고, 해당 컴포넌트를 테스트하는 방법을 살펴보겠습니다.
1. 새로운 컴포넌트 생성
Greeting.js 파일 생성
// Greeting.js import React from 'react'; function Greeting() { return ( <div> <h2>Hello World!</h2> <p>It's good to see you.</p> </div> ); } export default Greeting;
2.App.js에서 컴포넌트 사용
// App.js import React from 'react'; import Greeting from './components/Greeting'; function App() { return ( <div className="App"> <Greeting /> </div> ); } export default App;
3.Greeting.test.js 파일 생성
import { render, screen } from "@testing-library/react"; import Greeting from "./Greeting"; test('Greeting 테스트 ==>', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // Act //... nothing //Assert // Hello World 텍스트가 화면에 있는지 확인합니다. const helloWorldElement = screen.getByText('Hello World!', {exact:false}); expect(helloWorldElement).toBeInTheDocument(); });
4. 테스트 실행하기
npm test 실행
npm test
테스트 통과 확인
- 모든 테스트가 성공적으로 통과되었는지 확인합니다.
- 테스트 실패 시, 오류 메시지를 통해 어떤 부분에서 문제가 발생했는지 확인합니다.
4. 테스트 세부 사항
세 가지 A 과정
- Arrange (준비): 테스트할 컴포넌트를 렌더링합니다.
- Act (실행): 컴포넌트에서 특정 행동을 시뮬레이션합니다. (이번 예제에서는 필요하지 않음)
- Assert (단언): 예상 결과가 실제 결과와 일치하는지 확인합니다.
Matcher 함수
- expect 함수를 사용해 테스트 결과를 검증합니다.
- toBeInTheDocument 함수를 사용해 특정 엘리먼트가 문서에 있는지 확인합니다.
5. 테스트 유지 관리
- 테스트가 통과되도록 코드를 유지하거나, 필요한 경우 테스트를 업데이트합니다.
- 테스트가 실패할 경우, 원인을 파악하고 코드를 수정합니다.
이제 새로운 컴포넌트를 생성하고 테스트하는 방법을 익혔으므로, 더 복잡한 테스트를 작성할 준비가 되었습니다. 각 컴포넌트마다 테스트 파일을 따로 작성하는 습관을 가지면, 코드의 유지 보수성과 신뢰성을 높일 수 있습니다.
541.테스트 스위트와 함께 테스트 그룹화하기
테스트의 수가 많아질 때, 이를 정리하고 그룹화하는 것은 매우 중요합니다.
이렇게 하면 애플리케이션의 특정 컴포넌트나 기능에 대한 모든 테스트를 한 곳에 모아 관리할 수 있습니다.
이 작업을 돕기 위해, describe 함수를 사용해 테스트를 그룹화할 수 있습니다. 다음은 describe 함수를 사용해 테스트를 그룹화하는 방법에 대한 예시입니다.
테스트 그룹화하기
1. describe 함수를 사용한 테스트 그룹화
기존의 Greeting.test.js 파일을 수정하여, describe 함수를 사용해 테스트 그룹을 만듭니다.
// Greeting.test.js import { render, screen } from '@testing-library/react'; import Greeting from './Greeting'; describe('Greeting component', () => { test('renders Hello World as a text', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 단언: Hello World 텍스트가 화면에 있는지 확인합니다. const helloWorldElement = screen.getByText('Hello World!' , {exact:false}); expect(helloWorldElement).toBeInTheDocument(); }); test('renders a greeting message', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 단언: "It's good to see you." 텍스트가 화면에 있는지 확인합니다. const greetingMessage = screen.getByText("It's good to see you" ,{exact:false}); expect(greetingMessage).toBeInTheDocument(); }); });
위 코드에서는 describe 함수를 사용해 "Greeting component"라는 테스트 suite를 만들었습니다. 이 suite 안에는 두 개의 테스트가 있습니다. 이 suite는 Greeting 컴포넌트에 관련된 모든 테스트를 포함합니다.
2. describe 함수의 동작 방식
describe 함수는 두 개의 인자를 받습니다:
- 설명 텍스트: 테스트 suite의 이름을 설명합니다. 예를 들어, "Greeting component".
- 콜백 함수: 이 함수 내에 여러 개의 test 함수를 포함할 수 있습니다. 각 test 함수는 이 suite에 속하는 개별 테스트를 정의합니다.
테스트 실행 결과
이제 npm test를 실행하면, 콘솔에 출력되는 결과는 다음과 같습니다:
PASS src/components/Greeting.test.js Greeting component ✓ renders Hello World as a text (x ms) ✓ renders a greeting message (x ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: x.x s
여기서 볼 수 있듯이, "Greeting component"라는 이름 아래 두 개의 테스트가 그룹화되어 있습니다. 각 테스트의 결과가 출력되고, 모든 테스트가 성공했음을 확인할 수 있습니다.
정리
- describe 함수를 사용해 테스트를 그룹화하면 테스트 관리가 용이합니다.
- 테스트 suite는 애플리케이션의 특정 컴포넌트나 기능에 속하는 모든 테스트를 하나의 그룹으로 모아줍니다.
- 이해와 유지 보수가 쉬워지고, 애플리케이션이 커지더라도 테스트를 체계적으로 관리할 수 있습니다.
이제 describe 함수를 사용하여 테스트를 그룹화하는 방법을 익혔으므로, 애플리케이션의 다양한 컴포넌트와 기능에 대해 더 많은 테스트를 작성할 준비가 되었습니다.
542.사용자 상호 작용 및 State 테스트하기
Greeting 컴포넌트
// Greeting.js import React, { useState } from 'react'; const Greeting = () => { const [changedText, setChangedText] = useState(false); const changeTextHandler = () => { setChangedText(true); }; return ( <div> <h1>Hello World!</h1> {!changedText && <p>It's good to see you.</p>} {changedText && <p>Changed!</p>} <button onClick={changeTextHandler}>Change Text!</button> </div> ); }; export default Greeting;
Greeting 테스트
테스트 코드에서 중복된 테스트를 정리하고 명확하게 정리해보겠습니다.
// Greeting.test.js import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Greeting from './Greeting'; describe('Greeting component', () => { test('renders Hello World as a text', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 단언: Hello World 텍스트가 화면에 있는지 확인합니다. const helloWorldElement = screen.getByText('Hello World!', { exact: false }); expect(helloWorldElement).toBeInTheDocument(); }); test('renders a greeting message when the button is NOT clicked', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 단언: "It's good to see you." 텍스트가 화면에 있는지 확인합니다. const greetingMessage = screen.getByText("It's good to see you.", { exact: false }); expect(greetingMessage).toBeInTheDocument(); }); test('renders "Changed!" if the button was clicked', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 실행: 버튼을 클릭합니다. const buttonElement = screen.getByRole('button', { name: 'Change Text!' }); userEvent.click(buttonElement); // 단언: "Changed!" 텍스트가 화면에 있는지 확인합니다. const outputElement = screen.getByText('Changed!', { exact: false }); expect(outputElement).toBeInTheDocument(); }); test('does not render the initial greeting message if the button was clicked', () => { // 준비: Greeting 컴포넌트를 렌더링합니다. render(<Greeting />); // 실행: 버튼을 클릭합니다. const buttonElement = screen.getByRole('button', { name: 'Change Text!' }); userEvent.click(buttonElement); // 단언: "It's good to see you." 텍스트가 화면에 없는지 확인합니다. const outputElement = screen.queryByText("It's good to see you.", { exact: false }); expect(outputElement).toBeNull(); }); });
주요 수정 사항
- 중복된 테스트 제거: 동일한 기능을 확인하는 두 개의 테스트를 하나로 통합했습니다.
- 테스트 명확성 개선: 테스트 이름을 더 명확하게 하여 어떤 동작을 테스트하는지 쉽게 이해할 수 있도록 했습니다.
- queryByText 사용: 특정 텍스트가 존재하지 않는지 확인할 때 queryByText를 사용하여, 엘리먼트가 없을 경우 null을 반환하도록 했습니다.
이제 이 코드를 저장하고 테스트를 다시 실행하면, 모든 테스트가 통과하는지 확인할 수 있을 것입니다. npm test 명령어를 사용하여 테스트를 실행하면 됩니다. 모든 테스트가 성공적으로 통과된다면, 작성한 컴포넌트가 예상대로 작동하는 것을 확인할 수 있습니다.
543.연결된 컴포넌트 테스트하기
Output 컴포넌트는 단순히 props.children을 렌더링하는 역할을 합니다. Greeting 컴포넌트에서 이를 사용하도록 변경한 후, 기존 테스트가 여전히 통과하는지 확인해 보겠습니다.
1. Output 컴포넌트 만들기
먼저, 새로운 Output 컴포넌트를 만들어봅니다.
Output.js
import React from 'react'; const Output = (props) => { return <p>{props.children}</p>; }; export default Output;
2. Greeting 컴포넌트 수정하기
이제 Greeting 컴포넌트에서 문단을 직접 렌더링하는 대신, Output 컴포넌트를 사용하도록 수정합니다.
Greeting.js
import React, { useState } from 'react'; import Output from './Output'; const Greeting = () => { const [changedText, setChangedText] = useState(false); const changeTextHandler = () => { setChangedText(true); }; return ( <div> <h1>Hello World!</h1> {!changedText && <Output>It's good to see you.</Output>} {changedText && <Output>Changed!</Output>} <button onClick={changeTextHandler}>Change Text!</button> </div> ); }; export default Greeting;
모든 테스트가 통과하면, Output 컴포넌트를 추가하여 Greeting 컴포넌트를 수정했음에도 불구하고 테스트가 여전히 잘 작동함을 확인할 수 있습니다.
결론
Output 컴포넌트를 추가하고 Greeting 컴포넌트에서 이를 사용하도록 변경한 후에도 테스트가 잘 통과한다는 것은, 컴포넌트 간의 결합이 잘 동작함을 의미합니다. 또한, 이러한 방식으로 테스트를 작성하면 코드의 유지보수성과 재사용성이 높아집니다. 필요할 경우, Output 컴포넌트에 대해 별도의 테스트를 작성할 수도 있지만, 현재의 간단한 구조에서는 Greeting 컴포넌트 내에서 함께 테스트하는 것으로 충분합니다.
544.비동기 코드 테스트하기
1. Async 컴포넌트 만들기
먼저 Async 컴포넌트를 작성합니다. 이 컴포넌트는 useEffect를 사용하여 더미 API에서 데이터를 가져오고, 가져온 데이터를 화면에 렌더링합니다.
Async.js
import React, { 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 ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }; export default Async;
2. Async 컴포넌트에 대한 테스트 작성
Async 컴포넌트를 테스트하기 위해 Async.test.js 파일을 작성합니다. 여기에서는 비동기 데이터 가져오기를 테스트하기 위해 findAllByRole을 사용합니다.
Async.test.js
import { render, screen } from '@testing-library/react'; import Async from './Async'; describe('Async component', () => { test('renders posts if request succeeds', async () => { render(<Async />); // 역할별로 모든 목록 항목(li)을 찾습니다. const listItemElements = await screen.findAllByRole('listitem'); // listItemElements의 길이가 0보다 클 것으로 예상됩니다. expect(listItemElements).not.toHaveLength(0); }); });
3. 테스트 실행
위의 테스트를 실행하여 모든 것이 올바르게 작동하는지 확인합니다. 터미널에서 다음 명령어를 실행합니다.
npm test
4. 테스트 설명
테스트에서는 비동기 요청의 성공 여부를 확인합니다. findAllByRole을 사용하여 역할이 listitem인 모든 요소를 찾습니다. 이 메서드는 프로미스를 반환하며, Jest가 해당 프로미스가 해결될 때까지 기다립니다. 요청이 성공하면 리스트 아이템이 렌더링되고, 테스트가 통과합니다.
추가 테스트
더미 API 호출이 실패하는 경우와 같은 다른 시나리오를 테스트할 수 있습니다. 예를 들어, API 호출이 실패할 때의 동작을 테스트할 수 있습니다.
모킹(mocking) API 호출
테스트 환경에서 네트워크 요청을 모킹하는 것이 좋습니다. 이렇게 하면 외부 API에 의존하지 않고 테스트를 실행할 수 있습니다.
모킹 예시
import { render, screen } from '@testing-library/react'; import Async from './Async'; beforeAll(() => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve([{ id: 'p1', title: 'First post' }]), }) ); }); describe('Async component', () => { test('renders posts if request succeeds', async () => { render(<Async />); const listItemElements = await screen.findAllByRole('listitem'); expect(listItemElements).not.toHaveLength(0); }); afterEach(() => { jest.clearAllMocks(); }); });
위 예시에서는 jest.fn을 사용하여 fetch를 모킹하고, 항상 성공적인 응답을 반환하도록 설정합니다. 테스트가 끝날 때마다 모킹된 함수를 정리합니다.
결론
Async 컴포넌트는 비동기 작업을 수행하는 예시로, 이를 테스트하는 방법을 배웠습니다. findAllByRole을 사용하여 비동기 작업을 처리하며,
모킹을 통해 외부 의존성을 제거함으로써 보다 견고한 테스트를 작성할 수 있습니다.
545.모의 작업
1. Fetch 요청 Mocking하기
테스트 중 실제 HTTP 요청을 보내지 않기 위해 fetch 함수를 모킹(mocking)합니다. 이는 다양한 시나리오(성공적인 응답, 오류 응답 등)를 시뮬레이션하여 컴포넌트가 각 상황에서 제대로 작동하는지 확인할 수 있게 해줍니다. 이를 통해 실제 서버에 요청을 보내지 않고도 테스트할 수 있습니다.
2. 테스트에 Mock 적용하기
Jest의 모킹 기능을 사용하여 fetch 함수를 모킹할 것입니다. 이를 통해 실제 fetch 대신 미리 정의된 응답을 반환하는 모의 함수를 사용합니다.
Async.test.js
다음은 모킹된 fetch 함수를 사용하는 업데이트된 테스트 파일입니다:
import { render, screen } from '@testing-library/react'; import Async from './Async'; describe('Async component', () => { beforeAll(() => { global.fetch = jest.fn(); }); afterEach(() => { jest.clearAllMocks(); }); test('요청이 성공하면 포스트를 렌더링함', async () => { // 성공적인 응답을 반환하도록 fetch를 모킹 fetch.mockResolvedValueOnce({ json: async () => [ { id: 'p1', title: '첫 번째 포스트' }, { id: 'p2', title: '두 번째 포스트' }, ], }); render(<Async />); // 포스트가 렌더링될 때까지 기다림 const listItemElements = await screen.findAllByRole('listitem'); expect(listItemElements).not.toHaveLength(0); }); test('요청이 실패하면 오류 메시지를 렌더링함', async () => { // 실패한 응답을 반환하도록 fetch를 모킹 fetch.mockRejectedValueOnce(new Error('데이터를 가져오는데 실패했습니다')); render(<Async />); // 오류 메시지가 렌더링될 때까지 기다림 const errorMessage = await screen.findByText('데이터를 가져오는데 실패했습니다'); expect(errorMessage).toBeInTheDocument(); }); });
3. 코드 설명
Fetch 함수 모킹:
- jest.fn()을 사용하여 전역 fetch 함수를 모킹합니다. 이를 통해 컴포넌트 내에서 사용되는 모든 fetch 호출이 이 모의 함수를 사용하게 됩니다.
- mockResolvedValueOnce를 사용하여 성공적인 fetch 호출을 시뮬레이션하고, 미리 정의된 값을 반환합니다.
- mockRejectedValueOnce를 사용하여 실패한 fetch 호출을 시뮬레이션하고, 오류를 반환합니다.
다양한 시나리오 테스트:
- 첫 번째 테스트 (요청이 성공하면 포스트를 렌더링함)에서는 모킹된 fetch가 포스트 목록을 반환하도록 설정하고, 리스트 아이템이 제대로 렌더링되는지 확인합니다.
- 두 번째 테스트 (요청이 실패하면 오류 메시지를 렌더링함)에서는 모킹된 fetch가 오류를 반환하도록 설정하고, 오류 메시지가 제대로 표시되는지 확인합니다.
4. 테스트 실행하기
테스트를 실행하려면 다음 명령어를 사용하세요:
npm test
이 명령어는 테스트를 실행하고, 컴포넌트가 성공적이고 실패한 fetch 요청 모두에 대해 올바르게 동작하는지 확인합니다.
5. Mocking의 장점
모킹은 여러 가지 장점을 제공합니다:
- 실제 네트워크 요청 없음: 서버에 불필요한 부하를 주지 않으며, 테스트 중 데이터가 변경되는 것을 방지합니다.
- 일관성: 네트워크 상태나 서버 가용성에 영향을 받지 않고 테스트를 수행할 수 있습니다.
- 제어 가능: 다양한 시나리오(성공, 실패, 느린 응답 등)를 쉽게 시뮬레이션할 수 있습니다.
- 독립성: 외부 의존성(fetch 함수)의 동작이 아니라 컴포넌트의 동작에 집중하여 테스트할 수 있습니다.
모킹을 통해 테스트는 더 신뢰할 수 있고, 빠르며, 외부 요인과 독립적으로 수행될 수 있습니다.
댓글 ( 0)
댓글 남기기