Vue3组件单元测试:Pinia状态变更未触发Checkbox更新排查
我在Vue3项目里有个展示Checkbox的组件,支持手动勾选,也会随Pinia状态自动切换勾选状态。刚接触单元测试,想验证Checkbox能正确响应Pinia状态变更,但测试环境里修改Pinia状态后,Checkbox值没变化(组件本身功能正常)。试过调用store action和store.$patch都没用。
组件代码
<template> <div class="field-checkbox"> <Checkbox role="checkbox" :aria-label="displayName" @change="checkGroupMember()" v-model="checked" :binary="true" /> <label>{{displayName}}</label> </div> </template> <script setup lang="ts"> import { useContactBookStore } from "@/stores/contactBookStore"; import { ref, watch } from "vue"; import { storeToRefs } from "pinia"; const store = useContactBookStore(); const props = defineProps({ groupMember: { type:Object, required: true } }); const checked = ref(false); const { getCheckedGroupMembers } = storeToRefs(store) const displayName = ref(props.groupMember.title + " " + props.groupMember.firstName + " " + props.groupMember.lastName); // 设置checkbox初始值 updateCheckBox(); // 监听store中选中成员变化,更新checkbox状态 watch(getCheckedGroupMembers , () => { updateCheckBox(); },{ deep: true }) // checkbox手动切换时,调用store方法更新状态 function checkGroupMember() { const groupMember = { id:props.groupMember.id, title:props.groupMember.title, firstName:props.groupMember.firstName, lastName:props.groupMember.lastName } store.checkGroupMember(groupMember,checked.value); } // 判断当前成员是否在选中列表中,更新checkbox状态 function updateCheckBox() { const groupMember = { id: props.groupMember.id, title: props.groupMember.title, firstName: props.groupMember.firstName, lastName: props.groupMember.lastName } const exists = getCheckedGroupMembers.value.find((member) => member.id === groupMember.id) checked.value = !!exists; } </script>
单元测试代码
import { render, screen } from "@testing-library/vue"; import GroupMember from "@/components/ContactBook/GroupMember.vue"; import { describe, it, vi, expect, beforeEach, afterEach } from "vitest"; import { createTestingPinia } from "@pinia/testing"; import PrimeVue from "primevue/config"; import { createPinia, setActivePinia } from "pinia"; import Checkbox from 'primevue/checkbox'; import { useContactBookStore } from "@/stores/contactBookStore"; describe("GroupMember", () => { const mockUser:GroupMember = {id:"TT001",title:"Mr",firstName:"Ted",lastName:"Tester"} let mockProps = {groupMember:mockUser}; render(GroupMember, { props: mockProps, global: { components: {Checkbox}, plugins: [PrimeVue, createTestingPinia({ initialState: {contactBook:{checkedGroupMembers:[mockUser]}}, stubActions: false, createSpy: vi.fn, fakeApp:true }), ], }, }); setActivePinia(createPinia()); it("Displays the user name in the correct format", async() => { const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName; screen.getByText(displayName) }); it("Shows the checkbox initially checked", async() => { let checkbox:any; const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName; checkbox = screen.getAllByRole("checkbox", { name: displayName })[1] expect(checkbox.checked).toBe(true) }); it("Should display the checkbox as unchecked when the store is updated", async() => { let checkbox:any; const displayName = mockProps.groupMember.title + " " + mockProps.groupMember.firstName + " " + mockProps.groupMember.lastName; checkbox = screen.getAllByRole("checkbox", { name: displayName })[1] const store = useContactBookStore(); await store.checkGroupMember(mockUser,false); //await store.$patch({checkedGroupMembers:[]}) // 这种方式也没用 expect(checkbox.checked).toBe(false) }); });
测试错误信息
54| expect(checkbox.checked).toBe(false) | ^ 55| }); 56| }); - Expected "false" + Received "true"
问题原因与解决方法
1. Pinia实例冲突
你在测试中先通过createTestingPinia创建了测试专用的Pinia实例并传给组件,但之后又调用setActivePinia(createPinia()),这会覆盖掉之前的测试Pinia,导致组件使用的Pinia和测试代码中获取的store不是同一个实例,状态变更自然无法同步到组件。
解决: 删除setActivePinia(createPinia())这一行,测试时直接使用createTestingPinia创建的实例即可。
2. 未等待组件状态更新
修改Pinia状态后,组件的watch回调需要时间执行并更新Checkbox的状态,测试中直接断言会导致断言时机过早,此时组件还未完成更新。
解决: 使用@testing-library/vue提供的waitFor包裹断言,等待组件状态更新完成。
3. Checkbox元素获取不准确
你用screen.getAllByRole("checkbox", { name: displayName })[1]获取Checkbox,索引[1]可能指向了错误的元素(比如label也可能被识别为checkbox角色),建议用更精确的查询方式,比如screen.getByRole("checkbox", { name: displayName }),如果确实存在多个元素,检查是否是组件重复渲染导致的,调整查询逻辑。
修改后的测试代码示例
import { render, screen, waitFor } from "@testing-library/vue"; // 引入waitFor import GroupMember from "@/components/ContactBook/GroupMember.vue"; import { describe, it, vi, expect } from "vitest"; import { createTestingPinia } from "@pinia/testing"; import PrimeVue from "primevue/config"; import Checkbox from 'primevue/checkbox'; import { useContactBookStore } from "@/stores/contactBookStore"; describe("GroupMember", () => { const mockUser:GroupMember = {id:"TT001",title:"Mr",firstName:"Ted",lastName:"Tester"} let mockProps = {groupMember:mockUser}; render(GroupMember, { props: mockProps, global: { components: {Checkbox}, plugins: [ PrimeVue, createTestingPinia({ initialState: {contactBook:{checkedGroupMembers:[mockUser]}}, stubActions: false, createSpy: vi.fn, fakeApp:true }), ], }, }); it("Displays the user name in the correct format", async() => { const displayName = `${mockProps.groupMember.title} ${mockProps.groupMember.firstName} ${mockProps.groupMember.lastName}`; screen.getByText(displayName); }); it("Shows the checkbox initially checked", async() => { const displayName = `${mockProps.groupMember.title} ${mockProps.groupMember.firstName} ${mockProps.groupMember.lastName}`; const checkbox = screen.getByRole("checkbox", { name: displayName }); expect(checkbox.checked).toBe(true); }); it("Should display the checkbox as unchecked when the store is updated", async() => { const displayName = `${mockProps.groupMember.title} ${mockProps.groupMember.firstName} ${mockProps.groupMember.lastName}`; const checkbox = screen.getByRole("checkbox", { name: displayName }); const store = useContactBookStore(); await store.checkGroupMember(mockUser,false); // 等待组件更新后再断言 await waitFor(() => { expect(checkbox.checked).toBe(false); }); }); });
内容的提问来源于stack exchange,提问作者user4094935

