Vue组件测试问题:无法获取Day组件的有效date props
Vue组件测试问题:无法获取fillArray生成的日期数组
我在为Vue组件Calendar.vue编写测试时,需要获取fillArray()函数生成的日期数组。尝试通过读取Day.vue组件的props来实现,但得到的数组全是null。推测fillArray函数在测试执行时未运行,试过flushPromises等方法也无法解决。
Calendar.vue组件代码
<script setup lang="ts"> import Day from '@/components/Day.vue' import { onMounted, ref } from 'vue' import Weekdays from '@/components/Weekdays.vue' import MonthSelector from '@/components/MonthSelector.vue' import { useDateStore } from '@/helpers/date' import CalendarHeader from '@/components/CalendarHeader.vue' const dateArray = ref<(Date)[]>([]); const selectedYear = ref(new Date().getFullYear()); const selectedMonth = ref(new Date().getMonth() + 1); const dateStore = useDateStore(); const numberOfDays = (year: number, month: number) => new Date(year, month, 0).getDate(); const getWeekday = (year: number, month: number, day: number) => new Date(year, month - 1, day).getDay(); const fillArray = () => { const daysInMonth = numberOfDays(selectedYear.value, selectedMonth.value); const firstWeekday = getWeekday(selectedYear.value, selectedMonth.value, 1); const totalDays = firstWeekday + daysInMonth; dateArray.value = new Array(totalDays).fill(null); let currentDay = 1; for (let i = firstWeekday; i < firstWeekday + daysInMonth; i++) { dateArray.value[i] = new Date(selectedYear.value, selectedMonth.value - 1, currentDay); currentDay++; } const previousMonthDays = numberOfDays(selectedYear.value, selectedMonth.value - 1); for (let i = firstWeekday - 1; i >= 0; i--) { dateArray.value[i] = new Date(selectedYear.value, selectedMonth.value - 2, previousMonthDays - (firstWeekday - 1 - i)); } }; const navigateForwards = () => { if (selectedMonth.value === 12) { selectedMonth.value = 1; selectedYear.value++; } else { selectedMonth.value++; } fillArray(); }; const navigateBackwards = () => { if (selectedMonth.value === 1) { selectedMonth.value = 12; selectedYear.value--; } else { selectedMonth.value--; } fillArray(); }; onMounted(() => { fillArray(); dateStore.initialize(); }); </script> <template> <div class="calendar"> <div class="calendar__table"> <CalendarHeader/> <MonthSelector :month="selectedMonth" :year="selectedYear" @navigateForwards="navigateForwards" @navigateBackwards="navigateBackwards"/> <Weekdays/> <div class="calendar__row"> <Day v-for="(date, index) in Array.from({ length: 7 }, (_, i) => dateArray[i] || null)" :key="index" :date="date" :ill="false" :vacation="false" /> </div> <div class="calendar__row"> <Day v-for="(date, index) in Array.from({ length: 7 }, (_, i) => dateArray[i + 7] || null)" :key="index + 7" :date="date" :ill="false" :vacation="false" /> </div> <div class="calendar__row"> <Day v-for="(date, index) in Array.from({ length: 7 }, (_, i) => dateArray[i + 14] || null)" :key="index + 14" :date="date" :ill="false" :vacation="false" /> </div> <div class="calendar__row"> <Day v-for="(date, index) in Array.from({ length: 7 }, (_, i) => dateArray[i + 21] || null)" :key="index + 21" :date="date" :ill="false" :vacation="false" /> </div> <div class="calendar__row"> <Day v-for="(date, index) in Array.from({ length: 7 }, (_, i) => dateArray[i + 28] || null)" :key="index + 28" :date="date" :ill="false" :vacation="false" /> </div> </div> </div> </template>
测试代码
import { flushPromises, mount, shallowMount } from '@vue/test-utils'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import Calendar from '@/components/Calendar.vue'; import { createPinia, setActivePinia } from 'pinia'; import MonthSelector from '@/components/MonthSelector.vue'; import { defineComponent } from 'vue'; vi.mock('@/helpers/date', () => ({ useDateStore: vi.fn(() => ({ initialize: vi.fn() })) })); describe('Calendar.vue', () => { let wrapper: any; const MockMonthSelector = defineComponent({ template: `<div></div>`, props: { month: Number, year: Number, }, methods: { navigateForwards() { this.$emit('navigateForwards'); }, navigateBackwards() { this.$emit('navigateBackwards'); }, }, }); const shallowMountCut = () => { const cut = mount(Calendar,{ global:{ components:{ MockMonthSelector } } }); return { getSelectedYear: () => cut.findComponent(MonthSelector).props('year'), setSelectedMonth: (number: number) => (cut.vm as any).selectedMonth = number, navigateBackwards: () => cut.findComponent(MockMonthSelector), findNavigateForwards: () => cut.findComponent(MockMonthSelector), findCalendarRow: () => cut.find('.calendar__row'), getDateArray: () => cut.findAllComponents({ name: 'Day' }).map((day) => day.props().date?.getDate() || null), }; }; beforeEach(() => { wrapper = shallowMount(Calendar); setActivePinia(createPinia()); wrapper.vm.$nextTick; }); it('should have correct amount of days', async () => { await flushPromises(); const { getDateArray } = shallowMountCut() expect(getDateArray()).toHaveLength(35); console.log(getDateArray()) }); });
当前测试结果
[ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null ]
我期望数组中包含有效的Date对象,后续测试需要用到这些值。
Day.vue代码
<script setup> import { computed, ref } from 'vue' import { useDateStore } from '@/helpers/date' const props = defineProps({ date: Date, ill: Boolean, vacation: Boolean, }); const store = useDateStore(); let day = ref<string>(); const convertDate = () => { if (props.date != undefined) { day.value = props.date.getDate().toString(); } return day.value; } const boxClass = computed(() => { if (props.ill) { return 'day-box__ill'; } else if (props.vacation) { return 'day-box__vacation'; } else { return 'day-box__normal'; } }); const isSelected = computed(() => { if (store.selectedDate == props.date) { return 'day-box__selected'; } else { return ''; } }); const valueSupplied = () => { return props.date != undefined; } const clicked = () => { if (props.date != undefined) { store.selectedDate = (props.date); } } </script> <template> <div v-if="valueSupplied()" :class="['day-box', boxClass, isSelected]" @click="clicked"> <b class="day-box__text">{{ convertDate() }}</b> </div> <div v-else class="day-box"/> </template>
问题修复方案
关键问题点
- Pinia初始化顺序错误:组件挂载前必须先创建并激活Pinia,否则组件内的
useDateStore无法获取实例,可能阻塞onMounted钩子执行。 - Mock组件注册名不匹配:原组件使用
<MonthSelector>,但测试中注册Mock组件时用了MockMonthSelector作为键名,导致原组件未被替换,渲染异常。 - 重复创建组件实例:测试用例中调用
shallowMountCut()会生成新的组件实例,未等待其生命周期完成就获取数据;同时beforeEach中的实例未被复用。 - 未等待DOM更新:
$nextTick仅被引用未调用,未等待fillArray执行完毕和视图更新。
修复后的测试代码
import { flushPromises, mount } from '@vue/test-utils'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import Calendar from '@/components/Calendar.vue'; import { createPinia, setActivePinia } from 'pinia'; import { defineComponent } from 'vue'; vi.mock('@/helpers/date', () => ({ useDateStore: vi.fn(() => ({ initialize: vi.fn() })) })); describe('Calendar.vue', () => { let wrapper: any; let cutHelpers: any; const MockMonthSelector = defineComponent({ template: `<div></div>`, props: { month: Number, year: Number, }, methods: { navigateForwards() { this.$emit('navigateForwards'); }, navigateBackwards() { this.$emit('navigateBackwards'); }, }, }); beforeEach(async () => { // 先初始化Pinia再挂载组件 setActivePinia(createPinia()); wrapper = mount(Calendar, { global: { components: { // 注册名与原组件一致 MonthSelector: MockMonthSelector } } }); // 等待onMounted执行和DOM更新 await flushPromises(); await wrapper.vm.$nextTick(); cutHelpers = { getDateArray: () => wrapper.findAllComponents({ name: 'Day' }).map((day) => day.props().date?.getDate() || null), }; }); it('should have correct amount of days and valid dates', async () => { const dateArray = cutHelpers.getDateArray(); expect(dateArray).toHaveLength(35); // 验证至少有部分非null值 expect(dateArray.some(val => val !== null)).toBe(true); console.log(dateArray); }); });
修复说明
- 调整Pinia初始化顺序:在
beforeEach中先调用setActivePinia(createPinia()),再挂载组件,确保组件能获取到Pinia实例。 - 修正Mock组件注册名:将
MockMonthSelector注册为MonthSelector,与原组件名匹配,确保组件正确渲染。 - 复用组件实例:在
beforeEach中挂载一次组件并等待生命周期完成,测试用例直接复用该实例的辅助方法,避免重复创建。 - 等待DOM更新:在挂载后调用
await flushPromises()和await wrapper.vm.$nextTick(),确保fillArray执行完毕且视图更新完成后再获取数据。
内容的提问来源于stack exchange,提问作者Leander
相关产品推荐
相关产品推荐

