[Day20] Vue 3 单元测试 (Unit Testing) - Form Elements Handling

几乎每个网站都会使用到表单元素 (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 和 Case 2 看起来很像,但两着的差别在於, Case 1 是验证 Input 栏位中是否有正确显示输入的值,Case 2 则是验证 v-model 是否有确定双向绑定成功,将输入的内容同步至绑定的变数上。

Case 1:於 input 输入值 [email protected] 後,是否有输入成功

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

  await input.setValue('[email protected]')

  expect(input.element.value).toBe('[email protected]')
})

语法说明:

  • setValue(): 要更改表单元素的值可以使用 setValue() 方法,setValue 接受一个参数,可以是字串或布林值,并且回传的是一个 Promise。
  • DOMWrapper: 透过 get() 或是 find() 成功找到目标元素时都会回传一个围绕 Wrapper API 的 DOM 元素的瘦包装器 (thin wrapper),而它有一个代表着 HTMLElement 的属性 element,又因为在上面的情况目标元素为 input tag 所以此时 element 真实的值其实为 HTMLInputElement

Case 2: 於 input 输入值 [email protected] 後,email 是否有相对应的值

test('after setted value, value of email should be [email protected]', async () => {
  const wrapper = mount(Component)

  await wrapper.get('[data-test="email"]').setValue('[email protected]')

  expect(wrapper.vm.email).toBe('[email protected]')
})

语法说明:

  • 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 = '[email protected]'
  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 = '[email protected]'
  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 ,那我们明天见!


<<:  [Day 21] Reactive Programming - Spring WebFlux(Hello World) Part 1

>>:  再谈中断与异常

资产剥离(divestiture)

首先考虑范围内的资产更为有效,因为业务中断,知识产权泄漏和数据隐私不合规是范围内资产所产生的影响或...

Day26 Data Storage in iOS 02 - Keychain & Property list (Plists)

Keychain Apple Keychain 是一个非常流行且功能强大的 Swift 工具,每个 ...

风险描述(risk descriptions)

根据ISO 31000,风险是“不确定性对目标的影响(effect of uncertainty o...

予焦啦!检验核心映像档:开机流程、OpenSBI 惯例、ELF 浅谈

本节是以 Golang 上游 1a708bcf1d17171056a42ec1597ca8848c...

今年我想陪着 30 天之 29

1304. Find N Unique Integers Sum up to Zero Given ...