前端单测学习(3)—— react组件单测进阶

1,783 阅读5分钟

前言

上一篇中我们已经对组件单测有了初步的经验,接下来我们针对组件的单测再补充改一些常见的case

判断样式

在react component中,常见会用一些字段来控制一些样式,或者通过props传入一些style,我们通过单测,要判断到这些样式是否正确的渲染出来

修改组件

TodoHeader组件增加多一个containerStyle的入参,用于控制最外层容器的样式,这里入参我们设置为optional就好,非必传

import "./index.css";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
  containerStyle?: React.CSSProperties;
}

export default function TodoHeader({ title, containerStyle }: TodoHeaderProps) {
  return (
    <div className="report-header" style={containerStyle}>
      <span className="title" data-testid="todo-header-title">
        {title}
      </span>
    </div>
  );
}

调用修改,增加样式入参

<TodoHeader
  title="这是一个标题"
  containerStyle={{ border: "1px solid blue" }}
/>

我们运行一下看看效果,pnpm start,可以看到border样式正确渲染了出来

image.png

单测编写

it(`正确渲染containerStyle的样式`, () => {
    const borderStyle = "1px solid blue";
    const containerStyle: CSSProperties = {
      border: borderStyle,
    };
    const { container } = render(
      <TodoHeader title="标题" containerStyle={containerStyle} />
    );
    expect(container.children[0]).toHaveStyle(`border: ${borderStyle}`);
});

这里我们用到了一个api,toHaveStyle来判断我们的样式是否有渲染出来,因为我们的样式是挂载在最顶层的div,所以我们直接用container.children[0]来获取这个div即可,当然可以换成跟我们上一篇文章里面用到的方法一样。
然后我们执行一下这个测试用例

pnpm test src/components/__tests__/todo-header.test.tsx

结果如下:

image.png 我们来试试失败的情况,如果现在我们把样式改了,看看这个用例是否会执行失败,这里改了入参时候的样式,改为了2px

it(`正确渲染containerStyle的样式`, () => {
    const borderStyle = "1px solid blue";
    const containerStyle: CSSProperties = {
      border: "2px solid blue",
    };
    const { container } = render(
      <TodoHeader title="标题" containerStyle={containerStyle} />
    );
    expect(container.children[0]).toHaveStyle(`border: ${borderStyle}`);
  });

看下运行结果,可以看到expected和received不符合,用例没有通过(这里我们是为了测试所以特地改了,我们还是改回去)

image.png

判断字段控制样式

在实际项目中我们会根据传入一些字段然后来区分展示不同的样式,最常见的就是各种状态展示不同的样式,这里我们也模拟一下这种场景

修改组件

改写一下之前的代码,增加多一个isFinish的字段,通过这个字段来显示不同的background

import "./index.css";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
  // 最外层容器的样式
  containerStyle?: React.CSSProperties;
  // 是否结束
  isFinish?: boolean;
}

export default function TodoHeader({
  title,
  containerStyle,
  isFinish = false,
}: TodoHeaderProps) {
  return (
    <div className="report-header" style={containerStyle}>
      <span
        className="title"
        data-testid="todo-header-title"
        style={{ background: isFinish ? "red" : "white" }}
      >
        {title}
      </span>
    </div>
  );
}

改一下调用的入口代码,看看效果,可以看到效果是有了(样式啥的就不吐槽了,毕竟只是为了做单测写的)

<TodoHeader
  title="这是一个标题"
  containerStyle={{ border: "1px solid blue" }}
  isFinish={true}
/>

image.png

单测编写

这里直接就是getByTestIdtoHaveStyle的组合就可以了

  it(`正确渲染isFinish为true的样式`, () => {
    const { getByTestId } = render(<TodoHeader title="标题" isFinish={true} />);
    const element = getByTestId("todo-header-title");
    expect(element).toHaveStyle(`background: red`);
  });

运行结果

image.png 这里不要忘了,我们还有一种为false的情况,因为我们的isFinish是boolean,只有true或者false两种情况,我们上面已经测试过true的情况了,我们再保证一下false的情况下也正常渲染

it(`正确渲染isFinish为false的样式`, () => {
    const { getByTestId } = render(
      <TodoHeader title="标题" isFinish={false} />
    );
    const element = getByTestId("todo-header-title");
    expect(element).toHaveStyle(`background: white`);
});

运行结果

image.png

判断属性

在react component中,给元素设置一些属性也是很常见的一种情况,比如我们传入图片url然后在组件中正确显示出来,根据这种情况我们就可以写一些单测

改写组件

我们增加多一个iconUrl的入参,如果有传入的时候我们就通过img标签把它显示出来,看下代码

import "./index.css";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
  // 最外层容器的样式
  containerStyle?: React.CSSProperties;
  // 是否结束
  isFinish?: boolean;
  // 图标的链接
  iconUrl?: string;
}

export default function TodoHeader({
  title,
  containerStyle,
  iconUrl,
  isFinish = false,
}: TodoHeaderProps) {
  return (
    <div className="report-header" style={containerStyle}>
      {iconUrl && <img src={iconUrl} />}
      <span
        className="title"
        data-testid="todo-header-title"
        style={{ background: isFinish ? "red" : "white" }}
      >
        {title}
      </span>
    </div>
  );
}

单测编写

这里用到了toHaveAttribute,得注意下这个api的入参,还用到了waitFor这个api,这里我们先不展开讨论,后面会详细说一说

it(`正确渲染显示图标的情况`, async () => {
    const iconUrl = "http://www.abc.com/test.png";
    const { container } = render(<TodoHeader title="标题" iconUrl={iconUrl} />);
    await waitFor(() => {
      const imgElement = container.querySelector("img");
      expect(imgElement).not.toBeNull();
      expect(imgElement).toHaveAttribute("src", iconUrl);
    });
});

运行结果

image.png

判断children

children在组件中也是比较常见的,给了组件多一些类似于插槽的功能,这里简单讲下我判断children的方法,其实思路比较简单,只不过是传入children之后通过获取元素判断其能否正常显示出来即可

改写组件

直接用PropsWithChildren增加了children的入参,显示children

import "./index.css";
import { PropsWithChildren } from "react";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
  // 最外层容器的样式
  containerStyle?: React.CSSProperties;
  // 是否结束
  isFinish?: boolean;
  // 图标的链接
  iconUrl?: string;
}

export default function TodoHeader({
  title,
  containerStyle,
  iconUrl,
  isFinish = false,
  children,
}: PropsWithChildren<TodoHeaderProps>) {
  return (
    <div className="report-header" style={containerStyle}>
      {iconUrl && <img src={iconUrl} />}
      <span
        className="title"
        data-testid="todo-header-title"
        style={{ background: isFinish ? "red" : "white" }}
      >
        {title}
      </span>
      {children}
    </div>
  );
}

单测编写

it(`正确渲染children的情况`, async () => {
    const id = "childrenId";
    const text = "这是一个文案";
    const { getByTestId } = render(
      <TodoHeader title="标题">
        <span data-testid={id}>{text}</span>
      </TodoHeader>
    );
    const childElement = getByTestId(id);
    expect(childElement).toHaveTextContent(text);
});

运行结果

image.png

判断ReactNode

和children比较类似,判断的方法也基本一致

改写组件

import "./index.css";
import { PropsWithChildren, ReactNode } from "react";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
  // 最外层容器的样式
  containerStyle?: React.CSSProperties;
  // 是否结束
  isFinish?: boolean;
  // 图标的链接
  iconUrl?: string;
  // 额外的信息
  extraInfo?: ReactNode;
}

export default function TodoHeader({
  title,
  containerStyle,
  iconUrl,
  isFinish = false,
  children,
  extraInfo,
}: PropsWithChildren<TodoHeaderProps>) {
  return (
    <div className="report-header" style={containerStyle}>
      {iconUrl && <img src={iconUrl} />}
      <span
        className="title"
        data-testid="todo-header-title"
        style={{ background: isFinish ? "red" : "white" }}
      >
        {title}
      </span>
      <span className="extra">{extraInfo}</span>

      {children}
    </div>
  );
}

单测编写

  it(`正确渲染extraInfo的情况`, async () => {
    const id = "extraId";
    const text = "这是一个文案";
    const { getByTestId } = render(
      <TodoHeader
        title="标题"
        extraInfo={<span data-testid={id}>{text}</span>}
      />
    );
    const childElement = getByTestId(id);
    expect(childElement).toHaveTextContent(text);
  });

运行结果

image.png

结尾

本篇博客补充了其他情况的一些组件单测情况,并把自己的一些经验写到了demo中,完整的代码可以看看这个commit

传送门

前端单测学习(1)—— 单测入门之react单测项目初步
前端单测学习(2)—— react 组件单测初步
前端单测学习(3)—— react组件单测进阶
前端单测学习(4)—— react 组件方法&fireEvent
前端单测学习(5)—— 快照
前端单测学习(6)—— 定时器
前端单测学习(7)—— mock
前端单测学习(8)—— react hook
前端单测学习(9)—— 覆盖率报告
前端单测学习(10)—— 状态管理redux
前端单测学习(11)—— react hook 进阶
前端单测学习(12)—— 性能优化
前端单测学习(13)—— 自动化测试

代码仓库: github.com/liyixun/rea…