如何在Vitest与Testing-Library中隔离单测的组件渲染?
用Vue3开发了一个简单列表组件,使用Vitest和Testing-Library编写自动化测试时,每个test方法渲染的组件会叠加在DOM中,导致getByText查询时抛出TestingLibraryElementError: Found multiple elements with the text: foo错误。
测试代码
import { describe, it, expect, test } from 'vitest' import { render, screen, fireEvent } from '@testing-library/vue' import TmpList from '../ui/TmpList.vue' const listItems = ['foo', 'bar'] describe('TmpList', () => { // 测试item-content插槽渲染 test('renders item-content slot', () => { const slotTemplate = ` <template v-slot:item-content="{ item }"> <div> {{ item }} </div> </template>`; render(TmpList, { props: { listItems }, slots: { 'item-content': slotTemplate } }); listItems.forEach(li => { expect(screen.getByText(li)).toBeTruthy(); }) }) // 测试列表项交互逻辑 test('should select item when clicked and is selectable', async () => { const slotTemplate = ` <template v-slot:item-content="{ item }"> <div> {{ item }} </div> </template>`; render(TmpList, { props: { listItems, selectable: true }, slots: { 'item-content': slotTemplate } }); const firstItem = screen.getByText(listItems[0]); await fireEvent.click(firstItem); expect(firstItem.classList).toContain('selected-item') }) })
组件代码
<template> <ul> <li v-for="(item, index) in listItems" :key="`list-item-${index}`" @click="onItemClick(index)" class="rounded mx-2" :class="{ 'selected-item bg-secondary-600/20 text-secondary': selectedIndex == index, 'hover:bg-zinc-200/30': selectable, }"> <slot name="item-content" :item="item"></slot> </li> </ul> </template> <script setup lang="ts"> import { computed, ref } from "vue"; export interface Props { listItems: any[]; selectable?: boolean; } const props = withDefaults(defineProps<Props>(), { selectable: false, }); const selectedIndex = ref<number>(-1); const onItemClick = (index: number) => { if (props.selectable) { selectedIndex.value = index; } }; </script>
终端错误信息
TestingLibraryElementError: Found multiple elements with the text: foo
Here are the matching elements:
Ignored nodes: comments, script, style
fooIgnored nodes: comments, script, style
foo(If this is intentional, then use the
*AllBy*variant of the query (likequeryAllByText,getAllByText, orfindAllByText)).Ignored nodes: comments, script, style
<li class="rounded mx-2" data-v-96593be0="" > <div> foo </div> </li> <li class="rounded mx-2" data-v-96593be0="" > <div> bar </div> </li> </ul>❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ getElementError node_modules/@testing-library/dom/dist/query-helpers.js:20:35 ❯ getMultipleElementsFoundError node_modules/@testing-library/dom/dist/query-helpers.js:23:10 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:55:13 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/components/__tests__/SUList.spec.ts:54:33 52| 53| render(TmpList, { props: { listItems, selectable: true }, slots: { 'item-content': slotTemplate } }); 54| const firstItem = screen.getByText(listItems[0]); | ^ 55| await fireEvent.click(firstItem); 56| expect(firstItem.classList).toContain('selected-item')<li class="rounded mx-2 hover:bg-zinc-200/30" data-v-96593be0="" > <div> foo </div> </li> <li class="rounded mx-2 hover:bg-zinc-200/30" data-v-96593be0="" > <div> bar </div> </li> </ul>
问题原因与解决办法
测试渲染叠加是因为每个test执行后,之前渲染的组件没有被清理出DOM。Testing Library Vue默认会自动清理,但如果未生效,可通过以下方式手动保证测试环境独立:
方法1:手动调用Testing Library的清理函数
在describe块内添加afterEach钩子,每次测试结束后清理DOM:
import { cleanup } from '@testing-library/vue' describe('TmpList', () => { afterEach(() => { cleanup() }) // 你的测试用例 })
方法2:直接清空DOM
利用Vitest的afterEach钩子,手动重置document.body内容:
describe('TmpList', () => { afterEach(() => { document.body.innerHTML = '' }) // 你的测试用例 })
方法3:使用render返回的卸载函数
每个render调用会返回包含unmount方法的对象,测试结束后调用它卸载当前组件:
test('renders item-content slot', () => { const slotTemplate = ` <template v-slot:item-content="{ item }"> <div> {{ item }} </div> </template>`; const { unmount } = render(TmpList, { props: { listItems }, slots: { 'item-content': slotTemplate } }); listItems.forEach(li => { expect(screen.getByText(li)).toBeTruthy(); }) unmount() // 测试结束后卸载组件 })
以上方法都能确保每个测试用例的渲染环境独立,避免元素叠加导致的查询错误。
内容的提问来源于stack exchange,提问作者Nicola Migliore

