如何使用Vitest测试含q-select的Vue组件并触发下拉列表显示
问题描述
我有一个基于Quasar的Vue组件,实现了输入至少4个字符后触发自动补全的功能,实际使用时输入内容能正常触发下拉选项,但在Vitest单元测试中无法触发filterFn函数,下拉列表始终不显示。
组件代码
<template> <q-select clearable :disable='disable' emit-value :error='error' error-message='Invalid Equipment Number' fill-input hide-dropdown-icon hide-selected hint='Minimum 4 characters to trigger autocompletion' input-debounce='100' :label='label' :loading='loading' map-options :model-value='mValue' :options='options' outlined :readonly='readonly' use-input @clear='onClear' @filter='filterFn' @input-value='setValue' > <template v-slot:no-option> <q-item> <q-item-section class='text-grey'>No results</q-item-section> </q-item> </template> </q-select> </template> <script lang="ts"> import EquipmentApi from 'modules/Config/models/Equipment/EquipmentApi' import { IEquipment } from 'modules/Config/models/Equipment/EquipmentInterface' import { defineComponent } from 'vue' export default defineComponent({ name: 'MyEquipment', props: { value: { required: false, type: Number }, label: { required: false, default: 'Equipment', type: String }, readonly: { type: Boolean, required: false }, disable: { type: Boolean, required: false }, error: { type: Boolean, default: false, required: false }, }, emits: { fill(equipment: IEquipment) { return equipment }, 'update:value'(val: number) { return val } }, data() { return { mValue: null as number|null, options: [] as number[], loading: false } }, computed: { isEquipment() { return (this.mValue)?.toString().length === 8 } }, watch: { value(val) { this.mValue = parseInt(val) } }, methods: { filterFn(val: any, update: any, abort:any) { if (val.length < 4) { abort() return } update(() => { this.loading = true EquipmentApi.filter(val).then((response: { data: number[] }) => { this.options = response.data this.loading = false }) }) }, setValue(val: string|number) { this.mValue = parseInt(val) this.$emit('update:value', this.mValue) }, onClear() { this.mValue = null }, }, }) </script>
单元测试代码
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-vitest' import { mount } from '@vue/test-utils' import EquipmentField from 'src/modules/Application/components/Form/EquipmentField.vue' import { describe, expect, it } from 'vitest' function sleep(milliseconds) { return new Promise((resolve) => { setTimeout(resolve, milliseconds) }) } installQuasarPlugin() let wrapper = null describe('Equipment field unit test', async () => { beforeEach(() => { wrapper = mount(EquipmentField) }) it('should have a list of equipment code after we fill the input, and the list\'s first row has text in it', async () => { wrapper.find('input').setValue('1234') sleep(2000) console.log(document.querySelectorAll('[role=option]').length) // length = 0. It means the dropdown is not created }) })
解决方案
1. 修复异步等待逻辑
你的sleep函数返回Promise,但测试中没有用await等待,导致代码还没等到防抖和接口请求完成就去查询DOM。修改测试:
it('should have a list of equipment code after we fill the input', async () => { wrapper.find('input').setValue('1234') await sleep(2000) // 加上await等待异步操作完成 await wrapper.vm.$nextTick() // 等待DOM更新 console.log(document.querySelectorAll('[role=option]').length) })
更高效的方式是用Vitest的模拟计时器替代真实等待,避免拖慢测试:
it('should have a list of equipment code after we fill the input', async () => { vi.useFakeTimers() wrapper.find('input').setValue('1234') vi.runAllTimers() // 快进所有计时器,跳过防抖和接口延迟 await wrapper.vm.$nextTick() // 等待DOM更新 console.log(document.querySelectorAll('[role=option]').length) vi.useRealTimers() // 恢复真实计时器 })
2. 模拟接口请求
测试中需要模拟EquipmentApi.filter的返回值,避免真实接口调用的不确定性。在测试文件顶部添加:
import EquipmentApi from 'modules/Config/models/Equipment/EquipmentApi' vi.mock('modules/Config/models/Equipment/EquipmentApi', () => ({ default: { filter: vi.fn().mockResolvedValue({ data: [12345, 12346] }) } }))
这样filterFn会获取到模拟的选项数据,确保options被正确填充。
3. 手动触发filter事件
如果自动触发有问题,可以直接手动调用组件的filter事件:
it('should trigger filterFn and load options', async () => { await wrapper.findComponent({ name: 'QSelect' }).vm.$emit('filter', '1234') await wrapper.vm.$nextTick() expect(wrapper.vm.options).toHaveLength(2) })
4. 确保输入框处于交互状态
Quasar的q-select在use-input模式下,需要输入框获得焦点才能触发输入逻辑:
it('should have a list of equipment code after we fill the input', async () => { const input = wrapper.find('input') await input.focus() await input.setValue('1234') vi.useFakeTimers() vi.runAllTimers() await wrapper.vm.$nextTick() console.log(document.querySelectorAll('[role=option]').length) vi.useRealTimers() })
内容的提问来源于stack exchange,提问作者Dika
相关产品推荐
相关产品推荐

