发布于 2026-01-05 3 阅读
0

独立开发和测试 React 组件

独立开发和测试 React 组件

编写前端代码很容易,但编写可靠的前端代码就难了。

现代用户界面比以往任何时候都更加复杂。因此,对自己的代码充满信心至关重要。将用户界面组件隔离开发和测试,就能帮助你实现这一点。

本文将介绍如何为 React 构建一个独立、可靠的图像组件。我们将使用StorybookTesting LibraryJest等工具。✨

如需跟着操作,请使用仓库作为模板。

安装依赖项后,运行命令yarn storybook启动 Storybook,运行命令yarn test --watch启动测试运行器。

我们先从测试开始。

在开始开发组件之前编写测试用例至关重要。我们希望组件能够满足测试用例的要求,而不是反过来。测试可以在开发过程中提供持续的反馈。一旦所有测试都通过,就可以确保代码能够按预期运行。

不过,有一点你必须牢记。由于代码质量是通过你编写的测试来验证的,因此编写好的测试至关重要。一个好的单元测试应该易读、快速、可靠,并且能够覆盖组件的所有不同使用场景。

现在我们来编写测试用例。

// src/components/Image/Image.test.tsx
import * as React from 'react';
import { fireEvent, render } from '@testing-library/react';

import { Image } from './';
import { photoSrc } from '../../constants';

const altText = 'abcd';

describe('Image', () => {
  it('should render the image properly', async () => {
    // render the Image component
    const { getByAltText } = render(<Image src={photoSrc} alt={altText} />);

    // retrieve a reference to the image
    const image = getByAltText(altText) as HTMLImageElement;

    // load the image
    fireEvent.load(image);

    // verify that the image exists on the DOM
    expect(image).toBeTruthy();

    // verify the src of the image
    expect(image.src).toEqual(photoSrc);
  });
});
Enter fullscreen mode Exit fullscreen mode

测试当然不会通过。我们还没有编写组件的标记代码。现在就来编写吧。

// src/components/Image/index.tsx
import React from 'react';
// import { fallbackSrc, loadingSrc } from '../../constants';

export interface ImageProps {
  src: string;
  alt: string;
  height?: string | number;
}

export const Image: React.FC<ImageProps> = ({ src, alt, height = '400px' }) => {
  return (
    <>
      <img
        src={src}
        alt={alt}
        style={{ height, width: 'auto', borderRadius: '10px' }}
      />
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

你会发现测试现在通过了。✔️太棒了!(如果出现错误,请重启测试运行程序。)

所有测试均通过。真是令人欣喜的景象!

等等……但是我们的组件长什么样呢?我们应该把它渲染到 React 应用的首页路由上吗?🤔

不,我们会使用 Storybook 来实现这个功能。

让我们为组件编写故事。

Storybook 记录了组件的渲染状态。我们为每个组件编写多个 Storybook,分别描述组件可以支持的不同状态。Storybook 允许我们独立开发 React 组件。如果您还不熟悉 Storybook,我强烈建议您阅读页面。

// src/components/Image.stories.tsx
import React from 'react';
import { Story, Meta } from '@storybook/react';

import { Image, ImageProps } from './';
import { photoSrc } from '../../constants';

export default {
  title: 'Example/Image',
  component: Image,
  argTypes: {
    src: { control: 'text' },
    alt: { control: 'text' }
  }
} as Meta;

const Template: Story<ImageProps> = args => <Image {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  src: photoSrc,
  alt: 'Sample alt text'
};
Enter fullscreen mode Exit fullscreen mode

图片来自 Unsplash

好了!我们的图像组件看起来简洁多了。但是目前它对加载状态和错误的处理还不完善。让我们为这两种情况编写测试。请将你的测试文件代码替换为以下内容:

// src/Image/Image.test.tsx
import * as React from 'react';
import { fireEvent, render } from '@testing-library/react';

import { Image } from './';
import { fallbackSrc, loadingSrc, photoSrc } from '../../constants';

const altText = 'abcd';

describe('Image', () => {
  it('should render the image properly', async () => {
    // render the Image component
    const { getByAltText } = render(<Image src={photoSrc} alt={altText} />);

    // retrieve a reference to the image
    const image = getByAltText(altText) as HTMLImageElement;

    // load the image
    fireEvent.load(image);

    // verify that the image exists on the DOM
    expect(image).toBeTruthy();

    // verify the src of the image
    expect(image.src).toEqual(photoSrc);
  });

  it('should display the loader until the image loads', async () => {
    const { getByAltText } = render(<Image src={photoSrc} alt={altText} />);

    const image = getByAltText(altText) as HTMLImageElement;

    // verify that the src of the image matches the loader. note that the image has not been loaded yet.
    expect(image.src).toEqual(loadingSrc);
  });

  it('should handle errors and render the fallback', async () => {
    const { getByAltText } = render(<Image src="#" alt={altText} />);

    const image = getByAltText(altText) as HTMLImageElement;

    // fire the error event for the image
    fireEvent.error(image);

    // verify that the src of the image matches our fallback
    expect(image.src).toEqual(fallbackSrc);
  });

  // an extra test case that verifies that our height prop behaves as expected
  it('should apply the provided height', async () => {
    const height = '200px';
    const { getByAltText } = render(
      <Image src={photoSrc} alt={altText} height={height} />
    );

    const image = getByAltText(altText) as HTMLImageElement;

    fireEvent.load(image);

    expect(image.style.height).toEqual(height);
  });
});
Enter fullscreen mode Exit fullscreen mode

我们还为该属性添加了一个额外的测试用例height。多一些(好的😉)测试用例总是有益无害的!

新增的三个测试中,有两个确实会失败。让我们重新审视一下组件的代码,并进行修改,使测试能够通过。请将组件的源代码修改为与以下内容一致:

// src/components/Image/index.tsx
import React from 'react';
import { fallbackSrc, loadingSrc } from '../../constants';

export interface ImageProps {
  src: string;
  alt: string;
  height?: string | number;
}

export const Image: React.FC<ImageProps> = ({ src, alt, height = '400px' }) => {
  // whether an error has occured or not
  const [err, setErr] = React.useState(false);

  // whether the image is loading or not
  const [loading, setLoading] = React.useState(true);

  return (
    <>
      <img
        // use the fallback image as src if an error has occured
        // use the loader image as src if the image is still loading
        src={!err ? (loading ? loadingSrc : src) : fallbackSrc}
        alt={alt}
        style={{ height, width: 'auto', borderRadius: '10px' }}
        // set loading to false once the image has finished loading
        onLoad={() => setLoading(false)}
        // set err to true if an error occurs
        onError={() => setErr(true)}
      />
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

这段代码乍一看可能令人望而生畏,但其实并不复杂。我们使用状态来跟踪两件事:图片是否仍在加载,以及是否发生了错误。然后,我们利用这些状态变量,根据条件渲染带有相应 src 属性的图片。就这么简单!

现在让我们编写一个 Story 来查看备用图片的实际效果。将以下代码添加到组件的 .stories 文件末尾。

export const Src404 = Template.bind({});
Src404.args = {
  src: '#',
  alt: 'something broke'
};
Enter fullscreen mode Exit fullscreen mode

就这样,我们为组件添加了一个故事!

就这样,我们为组件添加了一个新的故事(一个有点悲伤的故事)。您可以轻松切换组件的状态,查看组件的运行情况。这就是 Storybook 的强大之处!

所有测试均已通过!

你还会注意到,所有测试用例现在都通过了!🥳 这些绿色的勾号是不是很可爱?

就这样,我们已经成功地独立开发出了一个可靠的React 组件。

你一定对刚刚写的代码很有信心吧?感觉是不是很棒?🤩

😇 如果这篇文章对你有帮助,请在推特上关注我。我保证不会让你失望。

💡 如果您对此感兴趣,请访问componentdriven.org了解更多关于组件驱动开发流程的信息。

文章来源:https://dev.to/dhaiwat10/develop-test-react-components-in-isolation-3714