Vue Test Utils (VTU) - Vue.js 컴포넌트 테스트를 쉽게 만들어주는 유틸
Vue Test Utils Crash Course
<template>
<div></div>
</template>
<script>
export default {
name: 'TodoApp',
data() {
return {
todos: [
{
id: 1,
text: 'Learn Vue.js 3',
completed: false
}
]
}
}
}
</script>
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
test('renders a todo', () => {
const wrapper = mount(TodoApp)
const todo = wrapper.get('[data-test="todo"]')
expect(todo.text()).toBe('Learn Vue.js 3')
})
mount
importmount
- VTU에서 컴포넌트를 렌더링하는 주요 방법test
함수에 간단한 테스트 설명을 적어 테스트 선언mount
에 컴포넌트를 전달해 호출 (거의 모든 테스트에서 하게 될 일) → 결과를 wrapper
라는 변수에 저장 (convention, mount
가 앱을 감싸 테스트를 위한 편리한 메서드들을 제공하기 때문)expect
- 실제 값과 우리가 기대하는 값 매치data-test="todo"
를 가지는 요소를 찾아 text
메서드로 내용을 받아옴, 해당 내용이 'Learn Vue.js 3'
일 것이라 기대아직 todo가 없기 때문에 위 테스트를 그대로 돌리게 되면 get()
메서드가 반환할 wrapper
가 없어서 Unable to get [data-test="todo"]
에러 발생
TodoApp.vue
의 <template>
에서 todos
array를 렌더하도록 변경하면 테스트 패스 가능
<template>
<div>
<div v-for="todo in todos" :key="todo.id" data-test="todo">
{{ todo.text }}
</div>
</div>
</template>
유저가 새 todo를 추가하도록 하기 위해서는 유저가 text를 입력할 form 필요
유저가 form을 제출하면 새로운 todo가 렌더되어야 함 → 이에 대한 테스트 작성
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
test('creates a todo', () => {
const wrapper = mount(TodoApp)
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(1)
wrapper.get('[data-test="new-todo"]').setValue('New todo')
wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
테스트 코드 설명
mount
로 요소를 렌더링하는 것으로 시작<input>
을 업데이트하기 위해 setValue
사용<input>
업데이트 후 유저의 form 제출 동작을 시뮬레이션 하기 위해 trigger 사용테스트를 패스시키기 위해 TodoApp.vue
에 <form>
과 <input>
추가:
<template>
<div>
<div v-for="todo in todos" :key="todo.id" data-test="todo">
{{ todo.text }}
</div>
<form data-test="form" @submit.prevent="createTodo">
<input data-test="new-todo" v-model="newTodo" />
</form>
</div>
</template>
<script>
export default {
name: 'TodoApp',
data() {
return {
newTodo: '',
todos: [
{
id: 1,
text: 'Learn Vue.js 3',
completed: false
}
]
}
},
methods: {
createTodo() {
this.todos.push({
id: 2,
text: this.newTodo,
completed: false
})
}
}
}
</script>
정상 작동할 것 같지만 테스트는 실패하게 됨:
expect(received).toHaveLength(expected)
Expected length: 2
Received length: 1
Received array: [{"element": <div data-test="todo">Learn Vue.js 3</div>}]
todo의 갯수가 증가하지 않았음
Vue는 DOM을 비동기적으로 업데이트하기 때문에 테스트를 async
로 선언하고 DOM이 변경되는 메서드를 await
해야 함 (e.g. trigger
, setValue
등)
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
test('creates a todo', async () => {
const wrapper = mount(TodoApp)
await wrapper.get('[data-test="new-todo"]').setValue('New todo')
await wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
유저가 체크박스로 todo 아이템이 완료되었는지 표시할 수 있게 해주는 기능 구현
실패하는 테스트로 시작:
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
test('completes a todo', async () => {
const wrapper = mount(TodoApp)
await wrapper.get('[data-test="todo-checkbox"]').setValue(true)
expect(wrapper.get('[data-test="todo"]').classes()).toContain('completed')
})
완료된 todo에 completed
라는 클래스를 적용해줄 예정
<template>
<div>
<div
v-for="todo in todos"
:key="todo.id"
data-test="todo"
:class="[todo.completed ? 'completed' : '']"
>
{{ todo.text }}
<input
type="checkbox"
v-model="todo.completed"
data-test="todo-checkbox"
/>
</div>
<form data-test="form" @submit.prevent="createTodo">
<input data-test="new-todo" v-model="newTodo" />
</form>
</div>
</template>
완성!
위 예제를 보면 각 테스트의 코드 사이에 개행이 존재하는 경우가 있음:
import { mount } from '@vue/test-utils'
import TodoApp from './TodoApp.vue'
test('creates a todo', async () => {
const wrapper = mount(TodoApp)
await wrapper.get('[data-test="new-todo"]').setValue('New todo')
await wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
이 테스트는 개행으로 구별된 세 개의 단계 존재: arrange (준비), act (행동), assert (단언)
준비 단계 - 테스트를 위한 시나리오 셋업, 더 복잡한 테스트는 스토어 생성, DB 데이터 채워넣기 등 작업 필요
행동 단계 - 유저가 컴포넌트 또는 앱과 어떻게 상호작용할 지에 대한 시나리오의 시뮬레이션
단언 단계 - 우리가 기대하기에 현재 컴포넌트가 어떤 state를 가져야 하는지에 대한 단언
거의 모든 테스트는 이 세 단계를 따름, 이 가이드에서처럼 꼭 개행으로 나눠야 하는 것은 아니지만 이 세 단계를 염두에 두고 테스트를 작성하면 좋음
mount()
로 컴포넌트 렌더
get()
과 findAll()
로 DOM 쿼리
trigger()
와 setValue
로 유저 input 시뮬레이션
DOM 업데이트는 비동기로 이루어지므로 async
와 await
사용
테스트는 대개 준비, 행동, 단언의 세 단계로 구성됨