Svelte组件动画触发代码的Vitest单元测试疑问
问题:Svelte动画触发的代码能否进行单元测试?
我创建了一个Svelte组件,用于快速显示后消失,告知用户操作已执行及影响的记录数量。简化后的组件代码如下:
<script lang="ts"> import { type ShowStore } from "my-library"; import { fade, blur } from 'svelte/transition'; export let text: string; export let showStore: ShowStore; export let timeout = 500; function setExit() { console.log('setExit executed.'); setTimeout(() => showStore.hide(), timeout); } </script> <div class="rounded-5 display-2 p-5 translate-middle fw-bold top-50 start-50" in:blur={{ duration: 200 }} out:fade on:introend={setExit} > {text} </div>
上述CSS类来自Bootstrap,用于将元素显示在屏幕中央。
我想用Vitest编写单元测试,验证组件是否会通过timeout属性指定的时长后自动隐藏,但测试未能正常运行:setExit函数内的console.log语句未执行,这让我怀疑测试环境中动画是否会触发——如果setExit未被调用,是不是意味着入场动画没有完成?
ShowStore是一个工具类Svelte store,存储布尔值,包含show()、hide()、toggle()方法,底层基于writable<boolean>()实现。
单元测试代码如下:
test('Should hide itself after the specified timeout value.', async () => { // Arrange. const text = '+123'; const store = showStore(); store.show(); const timeout = 300; render(SwiftScore, { text, showStore: store, timeout }); const spy = vi.spyOn(store, 'hide'); // Act. await vi.waitFor(() => { console.log('Show value: %s', get(store)); if (get(store)) { return Promise.reject(); } }, timeout + 300); // Assert. expect(spy).toHaveBeenCalledOnce(); expect(get(store)).toEqual(false); });
当前使用的工具包括@testing-library/svelte、jsDom和Vitest。请问:由动画相关事件触发的代码能否进行单元测试?
解答
当然可以测试动画触发的代码,你的测试未运行是因为jsDom环境默认不会执行Svelte的过渡动画,也就不会触发introend事件,导致setExit从未被调用。以下是两种可行的解决思路:
1. 手动触发introend事件
既然测试环境中动画不会自动执行,直接获取组件元素,手动模拟触发introend事件即可跳过动画执行过程,聚焦逻辑验证:
test('Should hide itself after the specified timeout value.', async () => { // Arrange. const text = '+123'; const store = showStore(); store.show(); const timeout = 300; const { container } = render(SwiftScore, { text, showStore: store, timeout }); const spy = vi.spyOn(store, 'hide'); // 手动触发introend事件,模拟入场动画完成 container.firstElementChild.dispatchEvent(new Event('introend')); // Act. await vi.waitFor(() => { expect(get(store)).toBe(false); }, { timeout: timeout + 300 }); // Assert. expect(spy).toHaveBeenCalledOnce(); });
2. 结合假计时器加速测试流程
使用Vitest的vi.useFakeTimers()可以快进时间,同时配合手动触发动画事件,既贴近真实逻辑又提升测试速度:
test('Should hide itself after the specified timeout value.', async () => { vi.useFakeTimers(); // Arrange. const text = '+123'; const store = showStore(); store.show(); const timeout = 300; const { container } = render(SwiftScore, { text, showStore: store, timeout }); const spy = vi.spyOn(store, 'hide'); // 触发introend事件,启动后续超时逻辑 container.firstElementChild.dispatchEvent(new Event('introend')); // 快进时间至超时结束后 vi.advanceTimersByTime(timeout + 10); await vi.runAllTimersAsync(); // Assert. expect(spy).toHaveBeenCalledOnce(); expect(get(store)).toBe(false); vi.useRealTimers(); });
关键说明
- Svelte的过渡事件(如
introend、outroend)在jsDom中不会自动触发,因为jsDom不处理CSS动画或Svelte的JavaScript过渡逻辑。 - 单元测试的核心是验证业务逻辑正确性,而非动画效果本身,因此手动触发事件是完全合理的测试手段,无需在测试环境中完整运行动画。
内容的提问来源于stack exchange,提问作者José Ramírez
相关产品推荐
相关产品推荐

