React 컴포넌트 테스트

React 컴포넌트 테스트

CRA 테스트 샘플

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

// 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()
  }
)

컴포넌트 프레임 생성

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

export default function Icon(props) {
  return null
}

테스트 케이스 작성

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

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')
  })
})

@types/testing-library__jest-dom 타입 정의를 설치하면 jest-dom 커스텀 매처 사용이 손 쉬워집니다.

테스트 수행

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

npm test

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

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) 될 수 있도록 컴포넌트 코드를 작성합니다.

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

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

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

스크린 디버깅

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

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

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

props를 통해 전달 된 className 속성이 없을 경우, 조건 처리해 문제를 해결할 수 있도록 nullish 연산자를 활용해 코드를 다음과 같이 수정합니다.

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

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

Last updated