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>
原因分析
- 内部ref未暴露:组件仅通过
defineExpose对外暴露了close方法,modalDialog这个内部ref并未暴露,因此测试代码中wrapper.vm.modalDialog是undefined,直接赋值不会生效。 - 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
相关产品推荐
相关产品推荐

