Jest DOM 테스트

Jest DOM

RTL은 jest-dom 라이브러리의 사용법에 따라 매처(matchers)를 추가해 테스트를 수행합니다. 테스트 스위트(Suite)는 여러 테스트 케이스(Case)를 포함할 수 있지만, 케이스가 반드시 스위트 안에 포함되어야 하는 것은 아닙니다.

describe('테스트 스위트(Suite)', () => {
  test('테스트 케이스(Case)', () => {
    // 테스트 내용
  })
})

아래 코드는 테스트 스위트 안에 테스트 케이스를 포함하는 간단한 예입니다.

// SUITE
describe('진실은 진실하고, 거짓은 거짓이다.', () => {
  
  // CASE 1
  test('진실은 진실하다.', () => {
    expect(true).toBe(true);
  })
 
  // CASE 2
  test('거짓은 거짓이다.', () => {
    expect(false).toBe(false);
  })

})

테스트 시나리오

만들어야 할 로직을 테스트 하는 파일을 먼저 만듭니다.

import shoppingList from './shoppingList'

test('쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다.', () => {
  expect(shoppingList()).toContain('맥주')
})

.toContain() 메서드는 배열 아이템 포함 여부를 검사할 때 사용합니다.

테스트 결과는 FAIL이 나올겁니다. 아직 로직을 짜기 전이기 때문이죠.

npm test

[FAIL]  shoppingList.test.js
✕ 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다. (2 ms)

● 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다.

    expect(received).toContain(expected) // indexOf

    Expected value: "맥주"
    Received array: []

테스트 결과가 PASS가 되도록 코드 로직을 작성합니다.

const shoppingList = (...items) => [...items, '맥주']

테스트 결과 PASS 되면 성공입니다. 테스트를 통해 "기대" 값과 "실제" 값이 같음을 입증했고, 신뢰할 수 있습니다.

npm test

[PASS]  shoppingList.test.js
 ✓ 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다. (1 ms)

테스팅 파일 이름 규칙

Jest는 다음의 이름 규칙으로 테스트 파일을 찾아 테스팅을 수행합니다.

  • __tests__ 폴더 안의 .js 접미사가 있는 파일

  • .test.js 접미사가 있는 파일

  • .spec.js 접미사가 있는 파일

테스트 파일 위치

테스트 할 함수와 동일한 위치에 테스트 파일을 포함하는 것이 좋습니다. 테스트 할 파일에서 테스팅 할 파일을 불러오는 경로가 단순해져 관리가 쉽기 때문입니다.

src/utils/
 └── shoppingList/
     ├── shoppingList.js
     └── shoppingList.test.js
shoppingList.test.js
// 같은 위치에 있는 함수를 불러오는 경로가 복잡하지 않습니다.
import shoppingList from './shoppingList'

어설션(Assertion) 함수

toBeNull(), toBeInTheDocument()와 같은 어설션 함수를 말합니다. 각 어설션 함수(커스텀 매처)에 대한 상세한 사용법은 Custom Matchers 문서를 참고하세요.

toBeDisabled()

비할성(disabled) 요소인지 여부를 검사합니다.

<button data-testid="button" type="submit" disabled>전송</button>
<fieldset disabled><input type="text" data-testid="input" /></fieldset>
<a href="..." disabled>링크</a>
expect(getByTestId('button')).toBeDisabled() // true

expect(getByTestId('input')).toBeDisabled() // false

expect(getByText('링크')).not.toBeDisabled() // false

toBeEnabled()

not.toBeDisabled()와 동일합니다.

expect(getByText('링크')).toBeEnabled()

toBeEmpty()

요소가 포함하는 자식 노드를 포함하지 않는 지 여부를 검사합니다.

<span data-testid="not-empty">
  <span data-testid="empty"></span>
</span>
expect(getByTestId('empty')).toBeEmpty() // true
expect(getByTestId('not-empty')).not.toBeEmpty() // true

toBeEmptyDOMElement()

요소에 표시되는 콘텐츠가 없는 지 여부를 검사합니다. 주석은 무시하지만, 요소에 공백이 있으면 실패합니다.

<span data-testid="not-empty"><span data-testid="empty"></span></span>
<span data-testid="with-whitespace"> </span>
<span data-testid="with-comment"><!-- comment --></span>
expect(getByTestId('empty')).toBeEmptyDOMElement() // true
expect(getByTestId('not-empty')).not.toBeEmptyDOMElement() // true
expect(getByTestId('with-whitespace')).not.toBeEmptyDOMElement() // true
expect(getByTestId('with-comment')).toBeEmptyDOMElement() // true

toBeInTheDocument()

문서에 요소가 포함되어 있는 지 여부를 검사합니다.

<span data-testid="html-element">
  <span>Html Element</span>
</span>
<svg data-testid="svg-element"></svg>
expect(
  getByTestId(document.documentElement, 'html-element')
).toBeInTheDocument() // true

expect(
  getByTestId(document.documentElement, 'svg-element')
).toBeInTheDocument() // true

expect(
  queryByTestId(document.documentElement, 'does-not-exist')
).not.toBeInTheDocument() // true

toBeInvalid()

요소가 유효한 지 여부를 검사합니다. 값이 없는aria-invalid 속성을 가지고 있거나 "true" 값이 설정된 경우, checkValidity() 결과 값이 false인 경우 요소가 유효하지 않은 것으로 판단합니다.

<input data-testid="no-aria-invalid" />
<input data-testid="aria-invalid" aria-invalid />
<input data-testid="aria-invalid-value" aria-invalid="true" />
<input data-testid="aria-invalid-false" aria-invalid="false" />

<form data-testid="valid-form">
  <input />
</form>

<form data-testid="invalid-form">
  <input required />
</form>
expect(getByTestId('no-aria-invalid')).not.toBeInvalid() // true
expect(getByTestId('aria-invalid')).toBeInvalid() // true
expect(getByTestId('aria-invalid-value')).toBeInvalid() // true
expect(getByTestId('aria-invalid-false')).toBeInvalid() // false

expect(getByTestId('valid-form')).not.toBeInvalid()
expect(getByTestId('invalid-form')).toBeInvalid()

toBeRequired()

폼 요소가 필수 입력을 요구하는 지 여부를 검사합니다. required 또는 aria-required="true" 속성을 가진 경우 검사를 통과합니다.

<input data-testid="required-input" required />
<input data-testid="aria-required-input" aria-required="true" />
<input data-testid="conflicted-input" required aria-required="false" />
<input data-testid="aria-not-required-input" aria-required="false" />
<input data-testid="optional-input" />
<input data-testid="unsupported-type" type="image" required />
<select data-testid="select" required></select>
<textarea data-testid="textarea" required></textarea>
<div data-testid="supported-role" role="tree" required></div>
<div data-testid="supported-role-aria" role="tree" aria-required="true"></div>
expect(getByTestId('required-input')).toBeRequired()
expect(getByTestId('aria-required-input')).toBeRequired()
expect(getByTestId('conflicted-input')).toBeRequired()
expect(getByTestId('aria-not-required-input')).not.toBeRequired()
expect(getByTestId('optional-input')).not.toBeRequired()
expect(getByTestId('unsupported-type')).not.toBeRequired()
expect(getByTestId('select')).toBeRequired()
expect(getByTestId('textarea')).toBeRequired()
expect(getByTestId('supported-role')).not.toBeRequired()
expect(getByTestId('supported-role-aria')).toBeRequired()

toBeValid()

요소가 유효한 지 여부를 검사합니다. 값이 없는aria-invalid 속성을 가지고 있지 않거나 "false" 값이 설정된 경우, checkValidity() 결과 값이 true인 경우 요소가 유효한 것으로 판단합니다.

<input data-testid="no-aria-invalid" />
<input data-testid="aria-invalid" aria-invalid />
<input data-testid="aria-invalid-value" aria-invalid="true" />
<input data-testid="aria-invalid-false" aria-invalid="false" />

<form data-testid="valid-form">
  <input />
</form>

<form data-testid="invalid-form">
  <input required />
</form>
expect(getByTestId('no-aria-invalid')).toBeValid()
expect(getByTestId('aria-invalid')).not.toBeValid()
expect(getByTestId('aria-invalid-value')).not.toBeValid()
expect(getByTestId('aria-invalid-false')).toBeValid()

expect(getByTestId('valid-form')).toBeValid()
expect(getByTestId('invalid-form')).not.toBeValid()

toBeVisible()

요소가 사용자에게 보이는 지 여부를 검사합니다. 다음 요건에 모두 충족하면 검사를 통과합니다.

  • 요소가 문서에 포함되어 있습니다.

  • 요소의 display 스타일 속성 값이 none이 아닙니다.

  • 요소의 visibility 스타일 속성 값이 hidden 또는 collapse가 아닙니다.

  • 요소의 opacity 스타일 속성 값이 0 보다 큽니다.

  • 요소에 hidden 속성이 설정되지 않았습니다.

  • 요소를 포함하는 상위 요소가 문서에 포함되고, 보여야 합니다.

  • <details /> 요소의 open 속성이 설정되어 있습니다.

<div data-testid="zero-opacity" style="opacity: 0">Zero Opacity Example</div>
<div data-testid="visibility-hidden" style="visibility: hidden">
  Visibility Hidden Example
</div>
<div data-testid="display-none" style="display: none">Display None Example</div>
<div style="opacity: 0">
  <span data-testid="hidden-parent">Hidden Parent Example</span>
</div>
<div data-testid="visible">Visible Example</div>
<div data-testid="hidden-attribute" hidden>Hidden Attribute Example</div>
expect(getByText('Zero Opacity Example')).not.toBeVisible()
expect(getByText('Visibility Hidden Example')).not.toBeVisible()
expect(getByText('Display None Example')).not.toBeVisible()
expect(getByText('Hidden Parent Example')).not.toBeVisible()
expect(getByText('Visible Example')).toBeVisible()
expect(getByText('Hidden Attribute Example')).not.toBeVisible()

toContainElement()

요소가 다른 요소를 포함하는 지 여부를 검사합니다.

<span data-testid="ancestor">
  <span data-testid="descendant"></span>
</span>
const ancestor = getByTestId('ancestor')
const descendant = getByTestId('descendant')
const nonExistantElement = getByTestId('does-not-exist')

expect(ancestor).toContainElement(descendant)
expect(descendant).not.toContainElement(ancestor)
expect(ancestor).not.toContainElement(nonExistantElement)

toContainHTML()

요소가 HTML 요소를 표현하는 문자열을 포함하는 지 여부를 검사합니다.

<span data-testid="parent">
  <span data-testid="child"></span>
</span>
expect(getByTestId('parent')).toContainHTML('<span data-testid="child"></span>')

아마도 이 매치를 사용할 필요가 없을 것입니다.

toHaveAttribute()

요소가 특정 속성을 포함하는 지 여부를 검사합니다. expect.stringContaining 또는 expect.stringMatching을 사용해 속성에 특정 기대 값 또는 부분 일치가 있는지 선택적으로 확인할 수 있습니다.

<button data-testid="ok-button" type="submit" disabled>ok</button>
const button = getByTestId('ok-button')

expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')

expect(button).toHaveAttribute('type', expect.stringContaining('sub'))
expect(button).toHaveAttribute('type', expect.not.stringContaining('but'))

toHaveClass()

요소가 특정 class 이름을 가지고 있는 지 여부를 검사합니다. (정확한 검사가 필요한 경우 {exact: true} 설정)

<button data-testid="delete-button" class="btn extra btn-danger">
  Delete item
</button>
<button data-testid="no-classes">No Classes</button>
const deleteButton = getByTestId('delete-button')
const noClasses = getByTestId('no-classes')

expect(deleteButton).toHaveClass('extra')
expect(deleteButton).toHaveClass('btn-danger btn')
expect(deleteButton).toHaveClass('btn-danger', 'btn')
expect(deleteButton).not.toHaveClass('btn-link')

expect(deleteButton).toHaveClass('btn-danger extra btn', {exact: true}) // 요소에 클래스 집합이 정확히 있는지 확인합니다.
expect(deleteButton).not.toHaveClass('btn-danger extra', {exact: true}) // 예상(expect)보다 많은 경우 실패합니다.

expect(noClasses).not.toHaveClass()

toHaveFocus()

요소에 현재 포커스(초점) 이동이 되어 있는 지 여부를 검사합니다.

<div><input type="text" data-testid="element-to-focus" /></div>
const input = getByTestId('element-to-focus')

input.focus()
expect(input).toHaveFocus()

input.blur()
expect(input).not.toHaveFocus()

toHaveFormValues()

폼에 name 속성이 지정된 폼 컨트롤을 포함하고 있고, 지정된 값을 가지고 있는 지 여부를 검사합니다.

<form data-testid="login-form">
  <input type="text" name="username" value="jane.doe" />
  <input type="password" name="password" value="12345678" />
  <input type="checkbox" name="rememberMe" checked />
  <button type="submit">Sign in</button>
</form>
expect(getByTestId('login-form')).toHaveFormValues({
  username: 'jane.doe',
  rememberMe: true,
})

자세한 사용법은 공식 문서에서 안내하는 toHaveFormValue()를 참고하세요.

toHaveStyle()

요소에 특정 값이 적용 된 특정 CSS 속성이 있는 지 여부를 검사합니다. 요소 중 일부가 아닌, 모든 예상 속성이 적용되는 경우에만 일치합니다.

<button
  data-testid="delete-button"
  style="display: none; background-color: red"
>
  Delete item
</button>
const button = getByTestId('delete-button')

expect(button).toHaveStyle('display: none')
expect(button).toHaveStyle({display: 'none'})
expect(button).toHaveStyle(`
  background-color: red;
  display: none;
`)
expect(button).toHaveStyle({
  backgroundColor: 'red',
  display: 'none',
})
expect(button).not.toHaveStyle(`
  background-color: blue;
  display: none;
`)
expect(button).not.toHaveStyle({
  backgroundColor: 'blue',
  display: 'none',
})

계산 된 CSS 스타일 결과 값 반영

문서에서 현재 사용 중인 스타일시트에 정의 된 일부 규칙이 클래스 이름을 통해 요소에 적용되는 규칙에서도 작동합니다. 일반적인 CSS 우선 순위 규칙이 적용됩니다.

toHaveTextContent()

요소에 텍스트 내용이 포함되어 있는 지 여부를 검사합니다. 문자열 인자가 전달되면 요소 내용에 대해 부분 대/소문자를 구분하는 매치가 수행됩니다. 대/소문자를 구분하지 않는 매치를 수행하려면 정규 표현 식을 사용합니다.

<span data-testid="text-content">Text Content</span>
const element = getByTestId('text-content')

expect(element).toHaveTextContent('Content')
expect(element).toHaveTextContent(/^Text Content$/) // 전체 내용 매칭
expect(element).toHaveTextContent(/content$/i) // 대/소문자 구분 없이 매칭
expect(element).not.toHaveTextContent('content')

toHaveValue()

폼 컨트롤 요소에 지정 된 값이 있는지 여부를 검사합니다.

<input type="text" value="text" data-testid="input-text" />
<input type="number" value="5" data-testid="input-number" />
<input type="text" data-testid="input-empty" />
<select multiple data-testid="select-number">
  <option value="first">First Value</option>
  <option value="second" selected>Second Value</option>
  <option value="third" selected>Third Value</option>
</select>
const textInput = getByTestId('input-text')
const numberInput = getByTestId('input-number')
const emptyInput = getByTestId('input-empty')
const selectInput = getByTestId('select-number')

expect(textInput).toHaveValue('text')
expect(numberInput).toHaveValue(5)
expect(emptyInput).not.toHaveValue()
expect(selectInput).not.toHaveValue(['second', 'third'])

toHaveDisplayValue()

폼 컨트롤 요소에 지정 된 표시 값(사용자에게 보여지는 값)을 포함하는 지 여부를 검사합니다.

<label for="input-example">First name</label>
<input type="text" id="input-example" value="Luca" />

<label for="textarea-example">Description</label>
<textarea id="textarea-example">An example description here.</textarea>

<label for="single-select-example">Fruit</label>
<select id="single-select-example">
  <option value="">Select a fruit...</option>
  <option value="banana">Banana</option>
  <option value="ananas">Ananas</option>
  <option value="avocado">Avocado</option>
</select>

<label for="multiple-select-example">Fruits</label>
<select id="multiple-select-example" multiple>
  <option value="">Select a fruit...</option>
  <option value="banana" selected>Banana</option>
  <option value="ananas">Ananas</option>
  <option value="avocado" selected>Avocado</option>
</select>
const input = screen.getByLabelText('First name')
const textarea = screen.getByLabelText('Description')
const selectSingle = screen.getByLabelText('Fruit')
const selectMultiple = screen.getByLabelText('Fruits')

expect(input).toHaveDisplayValue('Luca')
expect(input).toHaveDisplayValue(/Luc/)
expect(textarea).toHaveDisplayValue('An example description here.')
expect(textarea).toHaveDisplayValue(/example/)
expect(selectSingle).toHaveDisplayValue('Select a fruit...')
expect(selectSingle).toHaveDisplayValue(/Select/)
expect(selectMultiple).toHaveDisplayValue([/Avocado/, 'Banana'])

toBeChecked()

폼 컨트롤(radio, checkbox) 요소가 체크된 상태인 지 여부를 검사합니다.

<input type="checkbox" checked data-testid="input-checkbox-checked" />
<input type="checkbox" data-testid="input-checkbox-unchecked" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div
  role="checkbox"
  aria-checked="false"
  data-testid="aria-checkbox-unchecked"
/>

<input type="radio" checked value="foo" data-testid="input-radio-checked" />
<input type="radio" value="foo" data-testid="input-radio-unchecked" />
<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
<div role="switch" aria-checked="true" data-testid="aria-switch-checked" />
<div role="switch" aria-checked="false" data-testid="aria-switch-unchecked" />
const inputCheckboxChecked = getByTestId('input-checkbox-checked')
const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
expect(inputCheckboxChecked).toBeChecked()
expect(inputCheckboxUnchecked).not.toBeChecked()
expect(ariaCheckboxChecked).toBeChecked()
expect(ariaCheckboxUnchecked).not.toBeChecked()

const inputRadioChecked = getByTestId('input-radio-checked')
const inputRadioUnchecked = getByTestId('input-radio-unchecked')
const ariaRadioChecked = getByTestId('aria-radio-checked')
const ariaRadioUnchecked = getByTestId('aria-radio-unchecked')
expect(inputRadioChecked).toBeChecked()
expect(inputRadioUnchecked).not.toBeChecked()
expect(ariaRadioChecked).toBeChecked()
expect(ariaRadioUnchecked).not.toBeChecked()

const ariaSwitchChecked = getByTestId('aria-switch-checked')
const ariaSwitchUnchecked = getByTestId('aria-switch-unchecked')
expect(ariaSwitchChecked).toBeChecked()
expect(ariaSwitchUnchecked).not.toBeChecked()

toBePartiallyChecked()

폼 컨트롤(checkbox) 요소가 부분적으로 체크되었는 지 여부를 검사합니다.

<input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
<input type="checkbox" checked data-testid="input-checkbox-checked" />
<input type="checkbox" data-testid="input-checkbox-unchecked" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div
  role="checkbox"
  aria-checked="false"
  data-testid="aria-checkbox-unchecked"
/>
<input type="checkbox" data-testid="input-checkbox-indeterminate" />
const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
const inputCheckboxChecked = getByTestId('input-checkbox-checked')
const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')

expect(ariaCheckboxMixed).toBePartiallyChecked()
expect(inputCheckboxChecked).not.toBePartiallyChecked()
expect(inputCheckboxUnchecked).not.toBePartiallyChecked()
expect(ariaCheckboxChecked).not.toBePartiallyChecked()
expect(ariaCheckboxUnchecked).not.toBePartiallyChecked()

inputCheckboxIndeterminate.indeterminate = true
expect(inputCheckboxIndeterminate).toBePartiallyChecked()

toHaveDescription()

요소에 설명이 있는 지 여부를 검사합니다. aria-describedby 속성에 연결된 요소가 설명 내용을 포함합니다.

<button aria-label="Close" aria-describedby="description-close">X</button>

<!-- 설명 -->
<div id="description-close">Closing will discard any changes</div>

<button>Delete</button>
const closeButton = getByRole('button', {name: 'Close'})

expect(closeButton).toHaveDescription('Closing will discard any changes')
expect(closeButton).toHaveDescription(/will discard/) // 일부 매칭
expect(closeButton).toHaveDescription(expect.stringContaining('will discard')) // 일부 매칭
expect(closeButton).toHaveDescription(/^closing/i) // 대/소문자 구분 없이 매칭
expect(closeButton).not.toHaveDescription('Other description')

const deleteButton = getByRole('button', {name: 'Delete'})
expect(deleteButton).not.toHaveDescription()
expect(deleteButton).toHaveDescription('') // 설명이 없거나, 비어 있으면 항상 빈 문자열

Last updated

Was this helpful?