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

如何用Jest+React Testing Library测试React组件Switch分支并解决覆盖率问题

解决React Message组件switch语句测试覆盖率为0%的问题

问题描述

我编写了一个React的Message组件,其中的switch语句测试覆盖率始终为0%。已渲染组件、传递props并mock了props中的handleToasterClose函数,但仍无效果,怀疑问题卡在useEffect钩子处。不能使用Enzyme,请求仅基于Jest和React Testing Library提供解决方案。

组件代码

import React, { useEffect } from "react";
import { Row, RowBlock } from "./style-tags";
import { Icon } from "../icon";
import "./utp-toaster.css";
import icon from '../icons/icon-alert-warning.png';

const Message = props => {
    const {
        toasterType, message,
        handleToasterClose, iconPath='',
        showIcon=true, toasterTimeout=null
    } = props;
    let background;
    let toasterIcon;
    let color = "#000"

    useEffect(()=> {
        if(!!toasterTimeout) {
            setTimeout(()=>{
                handleToasterClose()
            }, toasterTimeout)
        }
    }, [])

    switch(toasterType) {
        case 'WARNING' :
            background = '#ffbc3d';
            toasterIcon = !!iconPath ? iconPath : icon
            break;
        case 'SUCCESS' :
            background = '#00AC3E';
            toasterIcon = !!iconPath ? iconPath : icon
            color = '#fff'
            break;
        case 'INFO' :
            background = '#005b9f';
            toasterIcon = !!iconPath ? iconPath : icon
            color = '#fff'
            break;
        default: 
            background = '#005b9f';
            toasterIcon = !!iconPath ? iconPath : icon
            break;
    }

    return (
        <Row background={background}>
            {showIcon && 
                <RowBlock width = "5%">
                    <div className="toaster-block">
                        {
                            !!toasterIcon &&
                            <img src={toasterIcon} alt="info" />
                        }
                    </div>
                </RowBlock>
            }
            <RowBlock width={`${!showIcon?'95%':'90%'}`}>
                <div className="toaster-block">
                    <p style={{'fontWeight':'bold', color}}>{message}</p>
                </div>
            </RowBlock>
            <RowBlock width="5%">
                <div className="toaster-block">
                    <p onClick={handleToasterClose}><Icon name="close" /></p>
                </div>
            </RowBlock>
        </Row>
    )
}

export default Message

原测试代码

import { render, screen } from "@testing-library/react"
import Message from "../Message"
test("test for warning", () => {
    const props = {
        toasterType: 'WARNING',
        message: "warning",
        handleToasterClose: jest.fn(),
        iconPath:'../icons.icon-alert-warning.png',
        showIcon:true,
        toasterTimeout:'9546845'
    } 
    let background;
    let toasterIcon;
    let color = "#000"

    render(<Message {...props} toasterType='WARNING' />)
})

解决方案

1. 修复useEffect的依赖与定时器逻辑

原useEffect依赖为空数组,导致toasterTimeout和handleToasterClose变更时不会重新执行,且未清理定时器易引发内存泄漏。同时toasterTimeout传入的是字符串,需转为数字类型才能被setTimeout正确识别:

useEffect(()=> {
  let timer;
  if (toasterTimeout !== null) {
    const timeoutNumber = Number(toasterTimeout);
    timer = setTimeout(() => {
      handleToasterClose();
    }, timeoutNumber);
  }
  // 组件卸载时清理定时器
  return () => clearTimeout(timer);
}, [toasterTimeout, handleToasterClose]); // 添加依赖项

2. 给关键元素添加data-testid

为方便测试中获取元素断言样式,给Row组件添加data-testid:

<Row background={background} data-testid="toaster-row">

同时修正消息文本的样式绑定,确保color变量生效:

<p style={{ fontWeight: 'bold', color }}>{message}</p>

3. 编写覆盖所有switch分支的测试用例

针对每个toasterType分支编写测试,断言样式、内容,并验证定时器行为:

测试WARNING分支

test("renders WARNING type message correctly", () => {
  const mockClose = jest.fn();
  render(
    <Message 
      toasterType="WARNING"
      message="Warning alert"
      handleToasterClose={mockClose}
      toasterTimeout={2000}
    />
  );

  // 断言背景色
  const toasterRow = screen.getByTestId("toaster-row");
  expect(toasterRow).toHaveStyle("background: #ffbc3d");
  
  // 断言文本颜色
  const messageText = screen.getByText("Warning alert");
  expect(messageText).toHaveStyle("color: #000");

  // 验证定时器触发关闭函数
  jest.runAllTimers();
  expect(mockClose).toHaveBeenCalledTimes(1);
});

测试SUCCESS分支

test("renders SUCCESS type message correctly", () => {
  render(
    <Message 
      toasterType="SUCCESS"
      message="Operation succeeded"
      handleToasterClose={jest.fn()}
    />
  );

  const toasterRow = screen.getByTestId("toaster-row");
  expect(toasterRow).toHaveStyle("background: #00AC3E");
  
  const messageText = screen.getByText("Operation succeeded");
  expect(messageText).toHaveStyle("color: #fff");
});

测试INFO分支

test("renders INFO type message correctly", () => {
  render(
    <Message 
      toasterType="INFO"
      message="Info notification"
      handleToasterClose={jest.fn()}
    />
  );

  const toasterRow = screen.getByTestId("toaster-row");
  expect(toasterRow).toHaveStyle("background: #005b9f");
  
  const messageText = screen.getByText("Info notification");
  expect(messageText).toHaveStyle("color: #fff");
});

测试default分支

test("renders default type message when toasterType is invalid or missing", () => {
  // 测试未传toasterType的情况
  render(
    <Message 
      message="Default message"
      handleToasterClose={jest.fn()}
    />
  );
  let toasterRow = screen.getByTestId("toaster-row");
  expect(toasterRow).toHaveStyle("background: #005b9f");

  // 测试传无效类型的情况
  render(
    <Message 
      toasterType="INVALID_TYPE"
      message="Invalid type message"
      handleToasterClose={jest.fn()}
    />
  );
  toasterRow = screen.getByTestId("toaster-row");
  expect(toasterRow).toHaveStyle("background: #005b9f");
});

4. 补充测试配置(可选)

若使用Jest,可在测试文件顶部添加定时器模拟:

jest.useFakeTimers();
afterEach(() => {
  jest.clearAllTimers();
});

内容的提问来源于stack exchange,提问作者Mohsin Ansari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:05:38