# React 컴포넌트 테스트

## CRA 테스트 샘플 <a href="#cra-test-sample" id="cra-test-sample"></a>

CRA 기본 템플릿을 통해 생성된 프로젝트 안에는 기본 제공되는 App 테스트 파일이 포함되어 있습니다.

{% tabs %}
{% tab title="src/App.test.js" %}

```jsx
// React 테스팅 라이브러리에서 render, screen 모듈 추출
import { render, screen } from '@testing-library/react'

// 테스팅 할 컴포넌트 로드
import App from './App';

// 테스트 케이스
test(
  'renders learn react link' /* React 학습 링크를 렌더링합니다. */, 
  () => {
    // App 컴포넌트를 렌더링
    render(<App />)
    // 'learn react' 문자 값이 매칭되는 요소를 찾아 linkElement에 할당합니다.
    const linkElement = screen.getByText(/learn react/i)
    // jest-dom의 어설션(assertions)을 사용해 linkElement가 
    // 문서 안에 포함되어 있는지 확인합니다.
    expect(linkElement).toBeInTheDocument()
  }
)
```

{% endtab %}
{% endtabs %}

## 컴포넌트 프레임 생성 <a href="#create-component-frame" id="create-component-frame"></a>

만들고자 하는 컴포넌트 프레임(틀, frame) 코드를 작성합니다.

{% tabs %}
{% tab title="components/Icon/Icon.js" %}

```jsx
export default function Icon(props) {
  return null
}
```

{% endtab %}
{% endtabs %}

## 테스트 케이스 작성 <a href="#write-test-case" id="write-test-case"></a>

만들고자 하는 컴포넌트의 테스트 파일을 먼저 만든 후, 컴포넌트가 갖춰야 할 테스트 코드를 작성합니다.

{% tabs %}
{% tab title="components/Icon/Icon.test.js" %}

```jsx
import { render, screen } from '@testing-library/react'
import Icon from './Icon'

// 테스트 스위트
describe('Icon 컴포넌트', () => {

  // 테스트 케이스 1
  test('Icon 컴포넌트는 img 요소입니다.', () => {
    render(<Icon />)
    const icon = screen.getByRole('img')
    expect(icon.nodeType).toBe(1)
  })
  
  // 테스트 케이스 2
  test('Icon 컴포넌트는 img 요소는 src, alt 속성을 반드시 입력 받아야 합니다.', () => {
    const src = 'up-arrow.svg'
    const alt = ''
    render(<Icon src={src} alt={alt} />)
    const icon = screen.getByRole('img')
    expect(icon).toHaveAttribute('src')
    expect(icon).toHaveAttribute('alt')
  })
  
  // 테스트 케이스 3
  test('Icon 컴포넌트는 img 요소는 src는 props에 전달된 src 값을 포함합니다.', () => {
    const src = 'up-arrow.svg'
    const alt = ''
    render(<Icon src={src} alt={alt} />)
    const icon = screen.getByAltText(alt)
    expect(icon.getAttribute('src').includes(src)).toBe(true)
  })

  // 테스트 케이스 4
  test('Icon 컴포넌트는 "icon" 클래스 이름을 포함합니다.', () => {
    const src = 'up-arrow.svg'
    const alt = ''
    render(<Icon src={src} alt={alt} />)
    const icon = screen.getByRole('img')
    expect(icon).toHaveClass('icon')
  })
})
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
[@types/testing-library\_\_jest-dom ](https://www.npmjs.com/package/@types/testing-library__jest-dom)타입 정의를 설치하면 [jest-dom 커스텀 매처](https://github.com/testing-library/jest-dom#custom-matchers) 사용이 손 쉬워집니다.

* [x] [toHaveAttribute()](/learning-react-app/tip-and-references/react-testing-library/jest-dom-test.md#tohaveattribute) → 속성 포함 여부 검사
* [x] [toHaveClass()](/learning-react-app/tip-and-references/react-testing-library/jest-dom-test.md#tohaveclass) → 클래스 이름 포함 여부 검사
  {% endhint %}

## 테스트 수행

테스트 케이스 작성 후에는 테스트 명령을 실행합니다.

```python
npm test
```

테스트 결과 작성한 모든 케이스가 실패(**FAIL**) 합니다. 아직 컴포넌트 로직을 작성하기 전이기 때문이죠.

![](/files/-MVLsjENQ0wV5dm1AsiQ)

```python
FAIL  src/components/Icon/Icon.test.js
  Icon 컴포넌트
    ✕ Icon 컴포넌트는 img 요소입니다. (37 ms)
    ✕ Icon 컴포넌트는 img 요소는 src, alt 속성을 반드시 입력 받아야 합니다. (7 ms)
    ✕ Icon 컴포넌트는 img 요소는 src는 props에 전달된 src 값을 포함합니다. (2 ms)
    ✕ Icon 컴포넌트는 "icon" 클래스 이름을 포함합니다. (9 ms)

  ● Icon 컴포넌트 › Icon 컴포넌트는 img 요소입니다.

    TestingLibraryElementError: Unable to find an accessible element with the role "img"

    There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole

    <body>
      <div />
    </body>

       5 |   test('Icon 컴포넌트는 img 요소입니다.', () => {
       6 |     render(<Icon />)
    >  7 |     const icon = screen.getByRole('img')
         |                         ^
       8 |     expect(icon.nodeType).toBe(1)
       9 |   })
      10 |

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByRole (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/components/Icon/Icon.test.js:7:25)

  ● Icon 컴포넌트 › Icon 컴포넌트는 img 요소는 src, alt 속성을 반드시 입력 받아야 합니다.

    TestingLibraryElementError: Unable to find an accessible element with the role "img"

    There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole

    <body>
      <div />
    </body>

      13 |     const alt = ''
      14 |     render(<Icon src={src} alt={alt} />)
    > 15 |     const icon = screen.getByRole('img')
         |                         ^
      16 |     expect(icon).toHaveAttribute('src')
      17 |     expect(icon).toHaveAttribute('alt')
      18 |   })

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByRole (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/components/Icon/Icon.test.js:15:25)

  ● Icon 컴포넌트 › Icon 컴포넌트는 img 요소는 src는 props에 전달된 src 값을 포함합니다.

    TestingLibraryElementError: Unable to find an element with the alt text: 

    <body>
      <div />
    </body>

      22 |     const alt = ''
      23 |     render(<Icon src={src} alt={alt} />)
    > 24 |     const icon = screen.getByAltText(alt)
         |                         ^
      25 |     expect(icon.getAttribute('src').includes(src)).toBe(true)
      26 |   })
      27 |

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByAltText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/components/Icon/Icon.test.js:24:25)

  ● Icon 컴포넌트 › Icon 컴포넌트는 "icon" 클래스 이름을 포함합니다.

    TestingLibraryElementError: Unable to find an accessible element with the role "img"

    There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole

    <body>
      <div />
    </body>

      30 |     const alt = ''
      31 |     render(<Icon src={src} alt={alt} />)
    > 32 |     const icon = screen.getByRole('img')
         |                         ^
      33 |     expect(icon).toHaveClass('icon')
      34 |   })
      35 | })

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByRole (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/components/Icon/Icon.test.js:32:25)

Test Suites: 1 failed, 1 total
Tests:       4 failed, 4 total
Snapshots:   0 total
Time:        0.678 s, estimated 1 s
```

## 컴포넌트 로직 작성

각 테스트 케이스 요구사항이 통과(PASS) 될 수 있도록 컴포넌트 코드를 작성합니다.

```bash
export default function Icon(props) {
  return (
    <img
      className={`icon ${props.className}`.trim()}
      src={props.src}
      alt={props.alt}
    />
  )
}
```

파일을 저장하면 테스트 결과가 출력됩니다. 모두 통과(PASS) 하여 요구사항을 충족했고 컴포넌트가 견고해졌습니다.

![](/files/-MVLscZ9u_eerHM24NW9)

```python
PASS  src/components/Icon/Icon.test.js
  Icon 컴포넌트
    ✓ Icon 컴포넌트는 img 요소입니다. (24 ms)
    ✓ Icon 컴포넌트는 img 요소는 src, alt 속성을 반드시 입력 받아야 합니다. (15 ms)
    ✓ Icon 컴포넌트는 img 요소는 src는 props에 전달된 src 값을 포함합니다. (4 ms)
    ✓ Icon 컴포넌트는 "icon" 클래스 이름을 포함합니다. (17 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.478 s, estimated 1 s
```

## 스크린 디버깅 <a href="#screen-debug" id="screen-debug"></a>

`screen` 객체의 `debug()` 메서드를 사용하면 렌더링 된 컴포넌트 코드를 Console에 출력합니다.

{% tabs %}
{% tab title="components/Icon/Icon.test.js" %}

```jsx
test('Icon 컴포넌트 스크린 테스트', () => {
  const src = 'up-arrow.svg'
  const alt = ''
  render(<Icon src={src} alt={alt} />)
  screen.debug()
})
```

{% endtab %}
{% endtabs %}

테스트 케이스는 모두 통과(**PASS**) 되었지만, 스크린 디버깅 출력 결과를 살펴보면 `class` 속성 이름에 문제가 있는 것을 확인할 수 있습니다. 모든 테스트 케이스를 통과했다고 해서 컴포넌트가 완전한 것이 아님을 깨달을 수 있습니다.

![](/files/-MVLvfDcOKG9spCiAws9)

`props`를 통해 전달 된 `className` 속성이 없을 경우, 조건 처리해 문제를 해결할 수 있도록 [nullish 연산자](/learning-react-app/tip-and-references/js-frequently-used-in-react/nullish-coalescing-operator.md)를 활용해 코드를 다음과 같이 수정합니다.

{% tabs %}
{% tab title="components/Icon/Icon.js" %}

```jsx
export default function Icon(props) {
  return (
    <img
      className={`icon ${props.className ?? ''}`.trim()}
      src={props.src}
      alt={props.alt}
    />
  )
}
```

{% endtab %}
{% endtabs %}

파일을 수정 후 저장하면 스크린 디버깅 결과가 `class` 이름이 정상적으로 렌더링 된 코드를 출력합니다.

![](/files/-MVLxDLOHO0QLUhXswTu)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yamoo9.gitbook.io/learning-react-app/tip-and-references/react-testing-library/react-component-test.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
