iT邦幫忙

2021 iThome 鐵人賽

DAY 20
2
Modern Web

前端工程師在工作中的各種實戰技能 (Vue 3)系列 第 20

[Day20] Vue 3 單元測試 (Unit Testing) - Form Elements Handling

  • 分享至 

  • xImage
  •  

幾乎每個網站都會使用到表單元素 (Form Elements),例如登入頁、註冊頁就有非常多個輸入框(<input>)在其中,又或者是網站的 header 中可能會有下拉選單(<select>),也因此我今天要來和大家分享如何為表單元素攥寫測試,如何為表單元素設置值與觸發事件。

https://ithelp.ithome.com.tw/upload/images/20211005/20113487o5CVb6jYRe.png

Interacting with input element

我們來看一個最基本的 input element 的例子。

import { ref } from 'vue'

const Component = {
  template: `
    <div>
      <input type="email" v-model="email" data-test="email" />
    </div>
  `,
  setup () {
    const email = ref('')

    return {
      email
    }
  }

在 Vue 中我們常見方法是使用 v-model 作雙向數據綁定,它會根據輸入類型自動選擇更新元素的正確方法,使我們可以輕鬆地使用表單元素。

要測試這個元件是否正常,我們應該驗證兩種情況是否正確運行:

  • Case 1: 於 input 輸入值 my@mail.com 後,是否有輸入成功。
  • Case 2: 於 input 輸入值 my@mail.com 後,email 是否有相對應的值。

Case 1 和 Case 2 看起來很像,但兩著的差別在於, Case 1 是驗證 Input 欄位中是否有正確顯示輸入的值,Case 2 則是驗證 v-model 是否有確定雙向綁定成功,將輸入的內容同步至綁定的變數上。

Case 1:於 input 輸入值 my@mail.com 後,是否有輸入成功

test('value of input element should be my@mail.com', async () => {
  const wrapper = mount(Component)
  const input = wrapper.get('[data-test="email"]')

  await input.setValue('my@mail.com')

  expect(input.element.value).toBe('my@mail.com')
})

語法說明:

  • setValue(): 要更改表單元素的值可以使用 setValue() 方法,setValue 接受一個參數,可以是字串或布林值,並且回傳的是一個 Promise。
  • DOMWrapper: 透過 get() 或是 find() 成功找到目標元素時都會回傳一個圍繞 Wrapper API 的 DOM 元素的瘦包裝器 (thin wrapper),而它有一個代表著 HTMLElement 的屬性 element,又因為在上面的情況目標元素為 input tag 所以此時 element 真實的值其實為 HTMLInputElement

Case 2: 於 input 輸入值 my@mail.com 後,email 是否有相對應的值

test('after setted value, value of email should be my@mail.com', async () => {
  const wrapper = mount(Component)

  await wrapper.get('[data-test="email"]').setValue('my@mail.com')

  expect(wrapper.vm.email).toBe('my@mail.com')
})

語法說明:

  • vm: vm 是 VueWrapper 的一個屬性,我們可以透過 vm 來取得 Vue instance。

Working with various form elements

我們來看一個更複雜的表單,它有更多的輸入類型,以及最後有一個 submit 的按鈕與行為。

import { reactive } from 'vue'

const Component = {
  template: `
    <form data-test="form" @submit.prevent="submit">
      <input data-test="email" type="email" v-model="form.email" />

      <textarea data-test="description" v-model="form.description" />

      <select data-test="city" v-model="form.city">
        <option value="taipei">Taipei</option>
        <option value="tainan">Tainan</option>
      </select>

      <input data-test="subscribe" type="checkbox" v-model="form.subscribe" />

      <input data-test="interval.weekly" type="radio" value="weekly" v-model="form.interval" />
      <input data-test="interval.monthly" type="radio" value="monthly" v-model="form.interval" />

      <button type="submit">Submit</button>
    </form>
  `,
  emits: ['submit'],
  setup (props, { emit }) {
    const form = reactive({
      email: '',
      description: '',
      city: '',
      subscribe: false,
      interval: ''
    })

    const submit = () => {
      emit('submit', form)
    }

    return {
      form,
      submit
    }
  }
}

要測試這個元件是否正常,我們應該驗證兩種情況是否正確運行:

  • Case 1: 每個欄位是否可以正常填寫。
  • Case 2: 填寫後,按下 submit 是否有將 form 的內容 emit 出去。

Case 1: 每個欄位是否可以正常填寫

test('fill up form', async () => {
  const wrapper = mount(Component)

  const email = 'name@mail.com'
  const description = 'Lorem ipsum dolor sit amet'
  const city = 'taipei'
  const subscribe = true

  await wrapper.get('[data-test="email"]').setValue(email)
  await wrapper.get('[data-test="description"]').setValue(description)
  await wrapper.get('[data-test="city"]').setValue(city)
  await wrapper.get('[data-test="subscribe"]').setValue()
  await wrapper.get('[data-test="interval.weekly"]').setValue()

  expect(wrapper.vm.form).toEqual({
    email,
    description,
    city,
    subscribe,
    interval: 'weekly'
  })
})

語法說明:

  • 在呼叫 setValue 的對象為 OPTION、CHECKBOX 或 RADIO 時, 如果沒有傳參數給 setValue 則表示為 checked 。

Case 2: 填寫後,按下 submit 是否有將 form 的內容 emit 出去

test('submits the form', async () => {
  const wrapper = mount(Component)

  const email = 'name@mail.com'
  const description = 'Lorem ipsum dolor sit amet'
  const city = 'taipei'
  const subscribe = true

  await wrapper.get('[data-test="email"]').setValue(email)
  await wrapper.get('[data-test="description"]').setValue(description)
  await wrapper.get('[data-test="city"]').setValue(city)
  await wrapper.get('[data-test="subscribe"]').setValue(subscribe)
  await wrapper.get('[data-test="interval.monthly"]').setValue()

  await wrapper.get('[data-test="form"]').trigger('submit.prevent')

  expect(wrapper.emitted('submit')[0][0]).toEqual({
    email,
    description,
    city,
    subscribe,
    interval: 'monthly'
  })
})

語法說明:

參考資料


今天的分享就到這邊,如果大家對我分享的內容有興趣歡迎點擊追蹤 & 訂閱系列文章,如果對內容有任何疑問,或是文章內容有錯誤,都非常歡迎留言討論或指教的!

明天要來分享的是 Vue3 單元測試 (Unit Testing) 主題的第六篇 Props & Computed ,那我們明天見!


上一篇
[Day19] Vue 3 單元測試 (Unit Testing) - Event Handling
下一篇
[Day21] Vue 3 單元測試 (Unit Testing) - Props & Computed
系列文
前端工程師在工作中的各種實戰技能 (Vue 3)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言