У меня есть простой компонент списка, написанный на Vue3, который я использую, чтобы научиться писать автоматические тесты с помощью Vitest и библиотеки тестирования. Однако кажется, что каждый метод test
визуализируется вместе, в результате чего мои вызовы getByText
выдают ошибку TestingLibraryElementError: найдено несколько элементов с текстом: 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', () => {
// Test item-content slot rendering
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 list item interaction
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
<div>
foo
</div>
Ignored nodes: comments, script, style
<div>
foo
</div>
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
Ignored nodes: comments, script, style
<body>
<div>
<ul
data-v-96593be0 = ""
>
<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>
</div>
<div>
<ul
data-v-96593be0 = ""
>
<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>
</div>
</body>
❯ 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')
Я знаю, что могу использовать метод getAllByText для запроса нескольких элементов, но в этом тесте я ожидаю, что будет найден только один элемент. Дублирование связано с рендерингом в тесте, а не с реальным компонентом.
Я что-то не так делаю при написании тестов? Есть ли способ гарантировать, что каждый рендер будет выполняться независимо от рендеринга из других тестов?
Каждый render()
возвращает методы @testing-library
(query* /get* /find* ), привязанные к отображаемому шаблону.
Другими словами, обычно им требуется параметр container
, но при возврате render
container
уже установлен в DOM этого конкретного render
:
it('should select on click', async () => {
const { getByText } = render(TmpList, {
props: { listItems, selectable: true },
slots: { 'item-content': slotTemplate },
})
const firstItem = getByText(listItems[0])
expect(firstItem).not.toHaveClass('selected-item')
await fireEvent.click(firstItem)
expect(firstItem).toHaveClass('selected-item')
})
Примечания:
fireEvent
больше не возвращает обещание в последних версиях @testing-library
. Если в версии, которую вы используете, по-прежнему возвращается обещание, оставьте async
- верно только для @testing-library/react
.screen
в свой набор тестовЕсли вы обнаружите, что пишете один и тот же селектор или одни и те же параметры рендеринга несколько раз, возможно, имеет смысл написать помощник renderComponent
в верхней части набора тестов:
describe(`<ListItems />`, () => {
// define TmpList, listItems, slotTemplate
const defaults = {
props: { listItems, selectable: true },
slots: { 'item-content': slotTemplate },
}
const renderComponent = (overrides = {}) => {
// rendered test layout
const rtl = render(TmpList, {
...defaults,
...overrides
})
return {
...rtl,
getFirstItem: () => rtl.getByText(listItems[0]),
}
}
it('should select on click', async () => {
const { getFirstItem } = renderComponent()
expect(getFirstItem()).not.toHaveClass('selected-item')
await fireEvent.click(getFirstItem())
expect(getFirstItem()).toHaveClass('selected-item')
})
it('does something else with different props', () => {
const { getFirstItem } = renderComponent({
props: /* override defaults.props */
})
// expect(getFirstItem()).toBeOhSoSpecial('sigh...')
})
})
Обратите внимание, что я распространяю rtl
в возвращаемом значении renderComponent()
, поэтому все методы get*
/find*
/query*
по-прежнему доступны для одноразового использования, для которых не стоит писать геттер.
Я использую его как для Vue, так и для React, и я полагаюсь на свою IDE, чтобы сказать мне, где async
является избыточным. Я почти уверен, что в пакете реагирования они удалили обещание, но я понятия не имею, запланировано ли это также для vue. Обновил ответ.
Спасибо за помощь, поигравшись с кодом и реализовав ваши предложения, теперь все работает, как я и ожидал. В качестве примечания для других, я использую
@testing-library/vue
, который в своей последней версии (6.6.1), кажется, все еще возвращает промисы при использованииfireEvent
.