在開發 Vue 應用時,測試邊界情況對於確保組件的穩定性和可靠性至關重要。本文將探討如何使用 TypeScript 和 Vitest 來測試 Vue 組件的各種邊界情況。我們將基於Day20先前開發的組件,如卡片、輸入框、等,展示如何編寫全面的測試用例。同時,展示如何在複雜的場景中進行測試。
建議讀者對測試有興趣的,可以嘗試自己把元件做出來後自己進行測試驗證。
讓我們從測試卡片組件開始。我們將測試不同的變體和邊界情況。
(檔案: src/components/Card.vue
)
<script setup lang="ts">
const { testID = 'card' } = defineProps<{
testID?: string;
}>();
</script>
<template>
<div :data-testid="testID" px-6 py-4 rounded-md shadow-xl bg-gray-100>
<slot />
</div>
</template>
(檔案:src/components/Card.spec.ts
)
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Card from './Card.vue';
describe('Card.vue', () => {
it('render default props', () => {
const wrapper = mount(Card);
expect(wrapper.attributes('data-testid')).toBe('card')
});
it('render props', () => {
const testID = 'hello';
const wrapper = mount(Card, {
props: {
testID,
}
});
expect(wrapper.attributes('data-testid')).toBe(testID)
});
it('render slot', () => {
const defaultSlotTemplate = 'this is cool';
const wrapper = mount(Card, {
slots: {
default: defaultSlotTemplate,
},
});
expect(wrapper.text()).toContain(defaultSlotTemplate);
});
it('UnoCss attribute', () => {
const wrapper = mount(Card);
expect(wrapper.attributes('px-6')).toBeDefined();
expect(wrapper.attributes('py-4')).toBeDefined();
expect(wrapper.attributes('rounded-md')).toBeDefined();
expect(wrapper.attributes('shadow-xl')).toBeDefined();
});
});
接下來,我們將測試輸入框組件,包括驗證和錯誤處理的邊界情況。
(檔案 : src/components/input/TextInputSmall.vue
)
<script setup lang="ts">
import { useId } from 'vue';
const { id = useId(), isShowLabel = true, errorMessage = '', disabled = false } = defineProps<{
label: string;
id?: string;
isShowLabel?: boolean;
errorMessage?: string;
disabled?: boolean;
}>();
const errorID = useId();
const modelValue = defineModel<string | number>({ default: '' });
</script>
<template>
<div>
<label v-show="isShowLabel" :for="id">{{ label }}</label>
<input
w-full
px-2 py-1
rounded-md
outline-none
shadow-lg
:aria-describedby="errorMessage ? errorID : undefined"
:id :disabled
v-model="modelValue"
/>
<span v-show="errorMessage" :id="errorID">{{ errorMessage }}</span>
</div>
</template>
(檔案: src/components/input/TextInputSmall.spec.ts
)
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils'
import TextInputSmall from './TextInputSmall.vue';
describe('TextInputSmall.vue', () => {
it('true', () => {
expect(true).toBe(true);
});
});
describe('TextInputSmall', () => {
it('renders the label when isShowLabel is true', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
isShowLabel: true
}
})
expect(wrapper.find('label').exists()).toBe(true)
expect(wrapper.find('label').text()).toBe('Test Label')
})
it('does not render the label when isShowLabel is false', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
isShowLabel: false
}
})
const label = wrapper.find('label');
expect(label.attributes('style')).toContain('display: none;');
})
it('binds the id prop to both label and input', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
id: 'test-id'
}
})
expect(wrapper.find('label').attributes('for')).toBe('test-id')
expect(wrapper.find('input').attributes('id')).toBe('test-id')
})
it('shows error message when provided', async () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
errorMessage: 'Error occurred'
}
})
expect(wrapper.find('span').exists()).toBe(true)
expect(wrapper.find('span').text()).toBe('Error occurred')
const inputElement = wrapper.find('input')
const errorSpan = wrapper.find('span')
expect(inputElement.attributes('aria-describedby')).toBe(errorSpan.attributes('id'))
})
it('does not show error message when not provided', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label'
}
})
const errorMessageSpan = wrapper.find('span');
expect(errorMessageSpan.attributes('style')).toContain('display: none;');
expect(errorMessageSpan.text()).toBe('');
})
it('disables the input when disabled prop is true', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
disabled: true
}
})
expect(wrapper.find('input').element.disabled).toBe(true)
})
it('updates the modelValue when input changes', async () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label',
modelValue: ''
}
})
await wrapper.find('input').setValue('New Value')
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['New Value'])
})
it('applies the correct CSS classes to the input', () => {
const wrapper = mount(TextInputSmall, {
props: {
label: 'Test Label'
}
})
const inputClasses = wrapper.find('input');
expect(inputClasses.attributes('w-full')).toBeDefined();
expect(inputClasses.attributes('px-2')).toBeDefined();
expect(inputClasses.attributes('py-1')).toBeDefined();
expect(inputClasses.attributes('rounded-md')).toBeDefined();
expect(inputClasses.attributes('outline-none')).toBeDefined();
expect(inputClasses.attributes('shadow-lg')).toBeDefined();
})
})
在本文中,我們深入探討了如何使用 TypeScript 和 Vitest 來測試 Vue 組件的邊界情況。我們針對卡片、輸入框等組件進行了全面的測試,涵蓋了各種可能的使用場景和邊界情況。
通過這些測試,我們不僅確保了組件在正常情況下的功能正確性,還驗證了它們在非預期輸入、異常狀態和極端情況下的行為。這種全面的測試方法有助於提高組件的穩定性和可靠性,同時也為未來的重構和功能擴展提供了保障。
在實際開發中,你可能需要根據具體的業務邏輯和用戶需求來調整和擴展這些測試。記住,好的測試不僅能捕捉錯誤,還能幫助你更好地理解和改進你的代碼。持續地編寫和維護測試,將有助於你構建更加健壯和可維護的 Vue 應用。