You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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元素。

解决方案步骤

  1. 修复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++;
      }
    }
    
  2. 优化测试代码(可选)
    如果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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 08:37:14