Jest测试调用findAllByRole无法获取全部role='alert'的Toast元素
问题描述
我实现了一个Toast组件,点击成功/错误按钮会生成对应提示框。使用Jest和@testing-library/dom编写测试时,循环点击成功按钮3次,但最终只获取到1个Toast元素,测试失败。
测试代码
import '@testing-library/jest-dom'; import { screen } from '@testing-library/dom'; import { getExampleDom } from '../test-utils'; test.only('`n` toasts appear on `n` button clicks', async () => { const { user } = getExampleDom(); const successBtn = screen.getByRole("button", { name: /Trigger success toast/i }); for(let i = 0; i < 3; i++) { user.click(successBtn); } const successToasts = await screen.findAllByRole("alert"); expect(successToasts).toHaveLength(3); });
测试工具代码(test-utils.js)
import userEvent from '@testing-library/user-event'; import Toast from "./Toast"; import { getEl } from "./utils"; export function getExampleDom() { document.body.innerHTML = ` <div id="controller"> <button id="successBtn">Trigger Success Toast</button> <button id="errorBtn">Trigger Error Toast</button> </div> `; /** * Test behaves unexpectedly after requiring index file * -> First test passes * -> Subsequent test fails * -> TODO: need to mock style, comment style import from index.js meanwhile. */ //require("./index"); /** require("../index"); not working correctly, writing js logic manually below*/ const toast = new Toast({ closable: true }); getEl('#successBtn').addEventListener('click', function handleSuccessBtnClick(e) { toast.success('Spawned a success toast'); }); getEl('#errorBtn').addEventListener('click', function handleErrorBtnClick(e) { toast.error('Spawned an error toast'); }); return { user: userEvent.setup() }; }
错误信息
● `n` toasts appear on `n` button clicks expect(received).toHaveLength(expected) Expected length: 3 Received length: 1 Received array: [<div aria-label="Spawned a success toast" class="toast toast__success" data-id="0" role="alert">Spawned a success toast<span class="toast-dismiss" data-close-id="0">x</span></div>] 24 | 25 | const successToasts = await screen.findAllByRole("alert"); > 26 | expect(successToasts).toHaveLength(3); | ^ 27 | });
问题分析与解决方案
核心原因
从错误返回的Toast元素data-id="0"可以看出:每次调用toast.success()时,并没有创建新的Toast节点,而是覆盖了同一个DOM元素。这说明你的Toast类的success方法逻辑存在问题——它没有在页面中追加新的Toast,而是复用/替换了已有的单个Toast元素。
解决方案步骤
修复Toast类的核心逻辑
确保success/error方法每次调用时都会创建新的DOM元素,并将其追加到Toast容器中,而非替换容器内容。示例逻辑如下:class Toast { constructor(options) { // 初始化全局Toast容器 this.container = document.createElement('div'); this.container.className = 'toast-container'; document.body.appendChild(this.container); this.options = options; this.idCounter = 0; // 用于标记每个Toast的唯一ID } success(message) { // 创建新的Toast元素 const toastEl = document.createElement('div'); toastEl.className = 'toast toast__success'; toastEl.setAttribute('role', 'alert'); toastEl.setAttribute('data-id', this.idCounter); toastEl.textContent = message; // 添加关闭按钮(如果配置了closable) if (this.options.closable) { const closeBtn = document.createElement('span'); closeBtn.className = 'toast-dismiss'; closeBtn.setAttribute('data-close-id', this.idCounter); closeBtn.textContent = 'x'; toastEl.appendChild(closeBtn); } // 将新Toast追加到容器 this.container.appendChild(toastEl); this.idCounter++; } }优化测试代码(可选)
如果Toast的创建是同步逻辑,可以去掉await,直接使用getAllByRole获取所有元素,提升测试效率:test.only('`n` toasts appear on `n` button clicks', () => { const { user } = getExampleDom(); const successBtn = screen.getByRole("button", { name: /Trigger success toast/i }); for(let i = 0; i < 3; i++) { user.click(successBtn); } const successToasts = screen.getAllByRole("alert"); expect(successToasts).toHaveLength(3); });若Toast包含动画延迟等异步渲染逻辑,则保留
await findAllByRole即可。
内容的提问来源于stack exchange,提问作者Stack
相关产品推荐
相关产品推荐

