如何用Vitest Mock方法测试Vue组件的onVisibleChange函数?
Vue组件单元测试:Mock
onChange验证onVisibleChange调用失败的解决方法 问题背景
需要Mock组件内的onChange方法,验证onVisibleChange是否正确调用它,但现有测试代码的两种尝试均报错:
- Spy断言失败,提示
onChange未被调用 - 替换实例方法后,内部调用仍未触发mock函数
组件代码(MyComponent.vue)
<script setup> import { ref } from "vue"; const isSelectAll = ref(true); const selectAllModel = ref(false); const onChange = () => { isSelectAll.value = true; selectAllModel.value = false; }; const onVisibleChange = () => { // do something than call onChange(); }; </script>
原测试代码及报错
import { mount } from 'vitest'; import { ref } from 'vue'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { const wrapper = shallowMount(MyComponent); const spy = vi.spyOn(wrapper.vm, 'onChange'); wrapper.vm.onVisibleChange(); expect(spy).toHaveBeenCalled(); expect(wrapper.vm.onChange).toHaveBeenCalled(); // 报错:AssertionError: expected "onChange" to be called at least once // 另一种尝试 const mock = vi.fn(); wrapper.vm.onChange = mock; wrapper.vm.onVisibleChange(); expect(mock).toHaveBeenCalled(); // 报错:AssertionError: expected "spy" to be called at least once expect(wrapper.vm.onChange).toHaveBeenCalled(); // 报错:TypeError: [Function onChange] is not a spy or a call to a spy! })
失败原因
<script setup>是闭包模式,内部定义的onChange是私有变量,不会挂载到组件实例wrapper.vm上。即使后续通过某种方式暴露,onVisibleChange内部调用的始终是闭包中的原始函数引用,外部替换实例上的方法不会影响内部的调用逻辑。
解决方法
方法1:用ref包裹函数实现可Mock的引用
修改组件代码,将onChange用ref包裹,让函数引用可被外部修改:
<script setup> import { ref } from "vue"; const isSelectAll = ref(true); const selectAllModel = ref(false); // 用ref包裹函数,使引用可修改 const onChange = ref(() => { isSelectAll.value = true; selectAllModel.value = false; }); const onVisibleChange = () => { // 调用ref的value属性 onChange.value(); }; // 暴露方法到组件实例 defineExpose({ onChange, onVisibleChange }); </script>
对应的测试代码:
import { shallowMount } from 'vitest'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { it('onVisibleChange should trigger onChange', () => { const wrapper = shallowMount(MyComponent); // 替换ref的value为mock函数 const mockOnChange = vi.fn(); wrapper.vm.onChange.value = mockOnChange; wrapper.vm.onVisibleChange(); expect(mockOnChange).toHaveBeenCalled(); }); });
方法2:提取逻辑到独立模块,Mock模块导出
将onChange的核心逻辑提取到单独的工具模块,测试时Mock该模块的导出:
- 创建
utils.js:
export const handleOnChange = (isSelectAll, selectAllModel) => { isSelectAll.value = true; selectAllModel.value = false; };
- 修改组件代码:
<script setup> import { ref } from "vue"; import { handleOnChange } from './utils.js'; const isSelectAll = ref(true); const selectAllModel = ref(false); const onChange = () => { handleOnChange(isSelectAll, selectAllModel); }; const onVisibleChange = () => { onChange(); }; </script>
- 测试代码:
import { shallowMount } from 'vitest'; import MyComponent from './MyComponent.vue'; import * as utils from './utils.js'; describe('MyComponent', () => { it('onVisibleChange should call onChange logic', () => { // Mock工具函数 const spy = vi.spyOn(utils, 'handleOnChange').mockImplementation(() => {}); const wrapper = shallowMount(MyComponent); wrapper.vm.onVisibleChange(); expect(spy).toHaveBeenCalled(); // 恢复原始函数 spy.mockRestore(); }); });
方法3:重构组件逻辑,通过props传递函数(适合复杂场景)
如果组件逻辑更复杂,可以将onChange设计为外部传入的props,测试时直接传入mock函数即可:
组件代码示例:
<script setup> const props = defineProps({ onChange: { type: Function, default: () => { // 默认逻辑 isSelectAll.value = true; selectAllModel.value = false; } } }); const isSelectAll = ref(true); const selectAllModel = ref(false); const onVisibleChange = () => { props.onChange(); }; </script>
测试代码:
import { shallowMount } from 'vitest'; import MyComponent from './MyComponent.vue'; describe('MyComponent', () => { it('onVisibleChange should call passed onChange', () => { const mockOnChange = vi.fn(); const wrapper = shallowMount(MyComponent, { props: { onChange: mockOnChange } }); wrapper.vm.onVisibleChange(); expect(mockOnChange).toHaveBeenCalled(); }); });
内容的提问来源于stack exchange,提问作者Ihsahs
相关产品推荐
相关产品推荐

