You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3模态框组件close方法测试报错:modalDialog.value.close不是函数

Vue3模态框组件MyModal的Vitest测试报错解决

问题重现

我编写了一个Vue3模态框组件MyModal,基于原生<dialog>元素实现:

// MyModal.vue
<script setup>
const emit = defineEmits(['close'])
const modalDialog = ref(null)

const close = () => {
  emit('close')
  modalDialog.value.close()
}
defineExpose({
  close
})
</script>

<template>
  <dialog ref="modalDialog">
    <h1>My Modal</h1>
    <slot />
  </dialog>
</template>

组件在浏览器中运行正常,但编写Vitest测试用例时,调用close方法出现错误:modalDialog.value.close is not a function。测试代码如下:

// MyModal.spec.js
const closeMock = vi.fn()
describe('MyModal', () => {
  test('close()', () => {
    const wrapper = mount(MyModal)
    wrapper.vm.modalDialog.value = {
      close: closeMock
    }

    wrapper.vm.close()
    expect(closeMock).toHaveBeenCalled()
  })
})

组件的使用示例:

<script setup>
import MyModal from './MyModal.vue'
const myModal = ref()

const openMyModal = () => {
  myModal.value.open()
}

const closeMyModal = () => {
  myModal.value.close()
}
</script>

<template>
  <my-modal ref="myModal" >
    <template #default>
      <MyComponent @close:modal="closeMyModal" />
    </template>
  </my-modal>
</template>

原因分析

  1. 内部ref未暴露:组件仅通过defineExpose对外暴露了close方法,modalDialog这个内部ref并未暴露,因此测试代码中wrapper.vm.modalDialog是undefined,直接赋值不会生效。
  2. JSDOM对<dialog>的支持限制:Vitest默认使用JSDOM模拟DOM环境,JSDOM对原生<dialog>元素的API支持不全,导致modalDialog.value(JSDOM创建的dialog元素)没有内置的close方法。

解决方案

方案1:全局Mock原生HTMLDialogElement的close方法

直接mockHTMLDialogElement.prototype.close,让所有dialog元素的close方法替换为mock函数:

// MyModal.spec.js
describe('MyModal', () => {
  test('close() 应触发close事件并调用dialog的close方法', () => {
    // Mock原生dialog的close方法
    const mockClose = vi.spyOn(HTMLDialogElement.prototype, 'close').mockImplementation(() => {})
    
    const wrapper = mount(MyModal)
    // 监听组件的close事件
    const closeEmitSpy = vi.spyOn(wrapper.emitted(), 'close')

    wrapper.vm.close()

    // 断言dialog的close方法被调用
    expect(mockClose).toHaveBeenCalled()
    // 断言组件触发了close事件
    expect(closeEmitSpy).toHaveBeenCalled()

    // 恢复mock
    mockClose.mockRestore()
  })
})

方案2:为组件内的dialog元素手动添加mock方法

如果不想全局mock,可以找到组件中的dialog元素,手动为其添加mock的close方法:

// MyModal.spec.js
describe('MyModal', () => {
  test('close() 应触发close事件并调用dialog的close方法', () => {
    const wrapper = mount(MyModal)
    const dialogEl = wrapper.find('dialog').element
    const mockClose = vi.fn()
    // 给dialog元素添加mock的close方法
    dialogEl.close = mockClose
    // 监听close事件
    const closeEmitSpy = vi.spyOn(wrapper.emitted(), 'close')

    wrapper.vm.close()

    expect(mockClose).toHaveBeenCalled()
    expect(closeEmitSpy).toHaveBeenCalled()
  })
})

方案3:暴露modalDialog ref(不推荐)

若一定要通过直接修改ref的方式测试,可以在组件中暴露modalDialog:

// MyModal.vue
<script setup>
const emit = defineEmits(['close'])
const modalDialog = ref(null)

const close = () => {
  emit('close')
  modalDialog.value.close()
}
// 新增暴露modalDialog
defineExpose({
  close,
  modalDialog
})
</script>

修改测试代码:

// MyModal.spec.js
describe('MyModal', () => {
  test('close()', () => {
    const closeMock = vi.fn()
    const wrapper = mount(MyModal)
    // 现在可直接访问modalDialog
    wrapper.vm.modalDialog.value = { close: closeMock }

    wrapper.vm.close()
    expect(closeMock).toHaveBeenCalled()
    expect(wrapper.emitted('close')).toBeTruthy()
  })
})

注意:此方式会暴露组件内部实现细节,不利于后续维护,不推荐使用。

补充说明

测试时建议同时验证两个行为:组件触发close事件、调用了dialog的close方法,确保逻辑完整性。若需测试模态框的打开逻辑,可通过同样方式mockHTMLDialogElement.prototype.showModal或show方法。

内容的提问来源于stack exchange,提问作者demiglace

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:53:18