无法通过Jest中的“提交"按钮触发Vuetify表单提交

问题描述:

我正在尝试使用Jest和Avoriaz验证基于Vuetify组件的Vue表单的行为.
我可以在表单上触发 submit.prevent ,从而导致预期的行为.
但是触发提交按钮上的 click 无效.

I'm trying to verify the behavior of a Vue form, based on Vuetify components, using Jest and Avoriaz.
I can trigger submit.prevent on the form, resulting in expected behavior.
But triggering click on the submit button does not work.

组件:

<template>
  <v-form
    ref="form"
    data-cy="form"
    @submit.prevent="login"
  >
    <v-text-field
      id="email"
      v-model="email"
      label="Email"
      name="email"
      prepend-icon="mdi-at"
      type="text"
      required
      autofocus
      data-cy="email-text"
    />
    <v-btn
      color="primary"
      type="submit"
      data-cy="login-btn"
    >
      Login
    </v-btn>
  </v-form>
</template>

<script>

export default {
  data () {
    return {
      email: 'test@test.com',
    }
  },
  computed: {},
  methods: {
    login: function () {
      console.log('Logging in')
    }
  }
}
</script>

测试设置:

import vuetify from '@/plugins/vuetify'
import { mount } from 'avoriaz'
import Form from '@/views/Form'

describe('Form', () => {
  const mountFunction = options => {
    return mount(Form, {
      vuetify,
      ...options
    })
  }

Vue&Vuetify设置在 @/plugins/vuetify 中完成:

Where the Vue & Vuetify setup is done in @/plugins/vuetify:

import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

export default new Vuetify({
})

以下测试成功(因此该模拟有效):

The following test succeeds (thus the mock works):

  it('can trigger form directly', () => {
    const login = jest.fn()
    const wrapper = mountFunction()
    wrapper.setData({ 'email': 'test@com' })
    wrapper.setMethods({ login })

    let element = wrapper.first('[data-cy=form]')
    element.trigger('submit.prevent')

    expect(login).toHaveBeenCalledTimes(1)
  })

但是实际上测试提交按钮失败:

But actually testing the submit button, fails:

  it('can trigger form through button', () => {
    const login = jest.fn()
    const wrapper = mountFunction()
    wrapper.setData({ 'email': 'test@test.com' })
    wrapper.setMethods({ login })

    const button = wrapper.first('[type=submit]')
    button.trigger('click')

    expect(login).toHaveBeenCalledTimes(1)
  })

更新:也许 package.json 中的一些相关依赖项:

Update: Perhaps some relevant dependencies in package.json:

{
  ..
  "dependencies": {
    "axios": "^0.19.1",
    "core-js": "^3.4.4",
    "vue": "^2.6.11",
    "vue-router": "^3.1.3",
    "vuetify": "^2.1.0",
    "vuex": "^3.1.2"
  },
  "devDependencies": {
    ..
    "avoriaz": "^6.3.0",
    "vue-jest": "^3.0.5",
    "vuetify-loader": "^1.3.0"
  }
}

更新:使用test-utils,非Vuetify组件(< form> < btn )时,以下测试成功:

Update: When using test-utils, non Vuetify components (<form> and <btn), the following test succeeds:

const localVue = createLocalVue()

localVue.use(Vuetify)
describe('Form', () => {
  const mountFunction = options => {
    return shallowMount(Form, {
      localVue,
      vuetify,
      ...options
    })
  }
  it('can trigger form through button alternative', async () => {
    const login = jest.fn()
    const wrapper = mountFunction({ attachToDocument: true })
    try {
      wrapper.setData({ 'email': 'test@test.com' })
      wrapper.setMethods({ login })

      const button = wrapper.find('[type=submit]')
      expect(button).toBeDefined()
      button.trigger('click')
      await Vue.nextTick()

      expect(login).toHaveBeenCalledTimes(1)
    } finally {
      wrapper.destroy()
    }
  })
})

然后切换到Vuetify组件会导致测试失败.

Then switching to Vuetify components cause the test to fail.

显然,在使用Vuetify组件(< v-form> < v-btn&>),要使其正常工作,需要一些关键要素:

Apparently, when using Vuetify components (<v-form> and <v-btn>), there are some key ingredients necessary to make it work:

  • Vue test-utils 代替avoriaz
  • mount 而不是 shallowMount
  • 包括 attachToDocument ,这也需要随后进行清理( wrapper.destroy())
  • 正如@Husam Ibrahim也提到的,
  • 需要异步严格地等待Vue.nextTick()
  • 要使setData完全生效,您可能需要额外地等待Vue.nextTick().在我的完整代码中,我进行了表单验证(在输入上使用:rules ,在表单上使用 v-model ,以及按钮的:disabled 绑定到 v-model 数据元素,这需要两个 nextTick() s
  • Vue test-utils instead of avoriaz
  • mount instead of shallowMount
  • include attachToDocument, which also requires cleaning up afterwards (wrapper.destroy())
  • as @Husam Ibrahim also mentioned, need to asynchronously await Vue.nextTick()
  • To let the setData have full effect, you might have to extra await Vue.nextTick(). In my full code I have form validation (with :rules on the input, and v-model on the form, and the button's :disabled bound to the v-model data element. This required two nextTick()s

以下工作方式(与问题中的'@/plugins/vuetify'相同:

The following works (with '@/plugins/vuetify' the same as in the question:

import vuetify from '@/plugins/vuetify'
import Vue from 'vue'
import { createLocalVue, mount } from '@vue/test-utils'
import Form from '@/views/Form'
import Vuetify from 'vuetify/lib'

const localVue = createLocalVue()

localVue.use(Vuetify)

describe('Form', () => {
  const mountFunction = options => {
    return mount(Form, {
      localVue,
      vuetify,
      ...options
    })
  }
  it('can trigger form through button alternative', async () => {
    const login = jest.fn()
    const wrapper = mountFunction({ attachToDocument: true })
    try {
      wrapper.setData({ 'email': 'test@test.com' })
      await Vue.nextTick() # might require multiple
      wrapper.setMethods({ login })

      const button = wrapper.find('[type=submit]')
      button.trigger('click')
      await Vue.nextTick()

      expect(login).toHaveBeenCalledTimes(1)
    } finally {
      wrapper.destroy()
    }
  })
})