Jest DOM
RTL은 jest-dom 라이브러리의 사용법 에 따라 매처(matchers)를 추가해 테스트를 수행합니다. 테스트 스위트(Suite)는 여러 테스트 케이스(Case)를 포함할 수 있지만, 케이스가 반드시 스위트 안에 포함되어야 하는 것은 아닙니다.
Copy describe('테스트 스위트(Suite)', () => {
test('테스트 케이스(Case)', () => {
// 테스트 내용
})
})
아래 코드는 테스트 스위트 안에 테스트 케이스를 포함하는 간단한 예입니다.
Copy // SUITE
describe('진실은 진실하고, 거짓은 거짓이다.', () => {
// CASE 1
test('진실은 진실하다.', () => {
expect(true).toBe(true);
})
// CASE 2
test('거짓은 거짓이다.', () => {
expect(false).toBe(false);
})
})
테스트 시나리오
만들어야 할 로직을 테스트 하는 파일을 먼저 만듭니다.
테스트 결과는 FAIL
이 나올겁니다. 아직 로직을 짜기 전이기 때문이죠.
테스트 명령 실행
Copy npm test
[FAIL] shoppingList.test.js
✕ 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다. (2 ms)
● 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다.
expect(received).toContain(expected) // indexOf
Expected value: "맥주"
Received array: []
테스트 결과가 PASS
가 되도록 코드 로직을 작성합니다.
테스트 결과 PASS
되면 성공입니다. 테스트를 통해 "기대" 값과 "실제" 값이 같음을 입증했고, 신뢰할 수 있습니다.
테스트 명령 실행
Copy npm test
[PASS] shoppingList.test.js
✓ 쇼핑리스트 기본 품목 중에는 `맥주`가 포함되어 있다. (1 ms)
테스팅 파일 이름 규칙
Jest는 다음의 이름 규칙으로 테스트 파일을 찾아 테스팅을 수행합니다.
__tests__
폴더 안의 .js
접미사가 있는 파일
테스트 파일 위치
테스트 할 함수와 동일한 위치에 테스트 파일을 포함하는 것이 좋습니다. 테스트 할 파일에서 테스팅 할 파일을 불러오는 경로가 단순해져 관리가 쉽기 때문입니다.
Copy src/utils/
└── shoppingList/
├── shoppingList.js
└── shoppingList.test.js
Copy // 같은 위치에 있는 함수를 불러오는 경로가 복잡하지 않습니다.
import shoppingList from './shoppingList'
어설션(Assertion) 함수
toBeNull() , toBeInTheDocument() 와 같은 어설션 함수를 말합니다. 각 어설션 함수(커스텀 매처)에 대한 상세한 사용법은 Custom Matchers 문서를 참고하세요.
toBeDisabled()
비할성(disabled) 요소인지 여부를 검사합니다.
Copy <button data-testid="button" type="submit" disabled>전송</button>
<fieldset disabled><input type="text" data-testid="input" /></fieldset>
<a href="..." disabled>링크</a>
Copy expect(getByTestId('button')).toBeDisabled() // true
expect(getByTestId('input')).toBeDisabled() // false
expect(getByText('링크')).not.toBeDisabled() // false
toBeEnabled()
not.toBeDisabled()
와 동일합니다.
Copy expect(getByText('링크')).toBeEnabled()
toBeEmpty()
요소가 포함하는 자식 노드를 포함하지 않는 지 여부를 검사합니다.
Copy <span data-testid="not-empty">
<span data-testid="empty"></span>
</span>
Copy expect(getByTestId('empty')).toBeEmpty() // true
expect(getByTestId('not-empty')).not.toBeEmpty() // true
toBeEmptyDOMElement()
요소에 표시되는 콘텐츠가 없는 지 여부를 검사합니다. 주석은 무시하지만, 요소에 공백이 있으면 실패합니다.
Copy <span data-testid="not-empty"><span data-testid="empty"></span></span>
<span data-testid="with-whitespace"> </span>
<span data-testid="with-comment"><!-- comment --></span>
Copy 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()
문서에 요소가 포함되어 있는 지 여부를 검사합니다.
Copy <span data-testid="html-element">
<span>Html Element</span>
</span>
<svg data-testid="svg-element"></svg>
Copy 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
인 경우 요소가 유효하지 않은 것으로 판단합니다.
Copy <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>
Copy 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"
속성을 가진 경우 검사를 통과합니다.
Copy <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>
Copy 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
인 경우 요소가 유효한 것으로 판단합니다.
Copy <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>
Copy 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
보다 큽니다.
요소를 포함하는 상위 요소가 문서에 포함되고, 보여야 합니다.
<details /> 요소의 open
속성이 설정되어 있습니다.
Copy <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>
Copy 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()
요소가 다른 요소를 포함하는 지 여부를 검사합니다.
Copy <span data-testid="ancestor">
<span data-testid="descendant"></span>
</span>
Copy 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 요소를 표현하는 문자열을 포함하는 지 여부를 검사합니다.
Copy <span data-testid="parent">
<span data-testid="child"></span>
</span>
Copy expect(getByTestId('parent')).toContainHTML('<span data-testid="child"></span>')
아마도 이 매치를 사용할 필요가 없을 것입니다.
사용자가 브라우저에서 앱을 인식하는 관점에서 테스트를 권장합니다. 그러므로 특정 DOM 구조에 대한 테스트는 수행하지 않는 것이 좋습니다. 사용자가 제어하는 DOM 구조를 확인하는 데 사용해서는 안 됩니다. 대신 toContainElement() 를 사용하세요.
테스트 중인 코드가 외부 소스에서 가져온 HTML을 렌더링하고 해당 HTML 코드가 의도한 대로 사용되었는지 확인하는 경우에 유용할 수 있습니다.
toHaveAttribute()
요소가 특정 속성을 포함하는 지 여부를 검사합니다. expect.stringContaining 또는 expect.stringMatching 을 사용해 속성에 특정 기대 값 또는 부분 일치가 있는지 선택적으로 확인할 수 있습니다.
Copy <button data-testid="ok-button" type="submit" disabled>ok</button>
Copy 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}
설정)
Copy <button data-testid="delete-button" class="btn extra btn-danger">
Delete item
</button>
<button data-testid="no-classes">No Classes</button>
Copy 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()
요소에 현재 포커스(초점) 이동이 되어 있는 지 여부를 검사합니다.
Copy <div><input type="text" data-testid="element-to-focus" /></div>
Copy const input = getByTestId('element-to-focus')
input.focus()
expect(input).toHaveFocus()
input.blur()
expect(input).not.toHaveFocus()
toHaveFormValues()
폼에 name
속성이 지정된 폼 컨트롤을 포함하고 있고, 지정된 값을 가지고 있는 지 여부를 검사합니다.
Copy <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>
Copy expect(getByTestId('login-form')).toHaveFormValues({
username: 'jane.doe',
rememberMe: true,
})
toHaveStyle()
요소에 특정 값이 적용 된 특정 CSS 속성이 있는 지 여부를 검사합니다. 요소 중 일부가 아닌, 모든 예상 속성이 적용되는 경우에만 일치합니다.
Copy <button
data-testid="delete-button"
style="display: none; background-color: red"
>
Delete item
</button>
Copy 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()
요소에 텍스트 내용이 포함되어 있는 지 여부를 검사합니다. 문자열 인자가 전달되면 요소 내용에 대해 부분 대/소문자를 구분하는 매치가 수행됩니다. 대/소문자를 구분하지 않는 매치를 수행하려면 정규 표현 식을 사용합니다.
Copy <span data-testid="text-content">Text Content</span>
Copy 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()
폼 컨트롤 요소에 지정 된 값이 있는지 여부를 검사합니다.
Copy <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>
Copy 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()
폼 컨트롤 요소에 지정 된 표시 값(사용자에게 보여지는 값)을 포함하는 지 여부를 검사합니다.
Copy <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>
Copy 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) 요소가 체크된 상태인 지 여부를 검사합니다.
Copy <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" />
Copy 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) 요소가 부분적으로 체크되었는 지 여부를 검사합니다.
Copy <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" />
Copy 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 속성에 연결된 요소가 설명 내용을 포함합니다.
Copy <button aria-label="Close" aria-describedby="description-close">X</button>
<!-- 설명 -->
<div id="description-close">Closing will discard any changes</div>
<button>Delete</button>
Copy 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('') // 설명이 없거나, 비어 있으면 항상 빈 문자열