前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >企业级 React 项目的高级测试设置

企业级 React 项目的高级测试设置

原创
作者头像
泽霖
发布2024-02-02 23:32:57
720
发布2024-02-02 23:32:57

在任何复杂应用中,测试是一个至关重要的方面。测试不仅仅是为了提高覆盖率,其主要目的是尽可能地模拟实际使用场景。

最近,我需要为一个庞大的ReactJS项目建立测试架构。

让我展示给你我是如何做的。虽然它还不完整,但我想与你分享我的进展。

为什么这么做?

该项目已经在使用Enzyme进行测试。

虽然Enzyme是一个不错的库,但是react-testing-library是测试React组件的更好选择。React团队也推荐使用它。

测试概述 - React

由于许多工程师在同一项目的不同部分上工作,建立一个共同的框架来处理常见用例是至关重要的。

测试场景

测试是任何良好的React应用程序的非常重要的部分。而react-testing-library是测试任何现代React应用程序的推荐方式。

虽然react-testing-library使根据组件的行为轻松直观地进行测试变得很容易,但有时设置要测试的组件可能会变得复杂。

接下来我们看看如何解决不同的场景下的问题

场景1:测试Redux连接的组件

测试仅由props控制的纯组件很容易。但往往情况并非如此。

如果组件依赖于redux状态,那么除非连接到redux状态,否则无法测试所有行为。

那么我们该怎么办呢?

首先,我们需要创建一个可重用的函数来渲染组件。这有点类似于ReactJS中的渲染属性模式。

它将接受一个store和一个初始状态作为参数。这些是你想要使用redux存储来测试组件的值。

代码语言:javascript
复制
import { render, RenderOptions } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { initialState as reducerInitialState, reducer, store } from 'reducer';

type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducer, initialState),
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <Provider store={store}>
      {children}
    </Provider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;

基本上,我们将store和初始状态作为函数的参数。

然后,我们用Redux提供的Provider将传递的组件包装起来。

现在,不再使用react-testing-library提供的默认渲染方法,你可以使用renderConnected函数测试你的组件,并传递你想要的存储部分。

代码语言:javascript
复制
import { screen } from '@testing-library/react';
import React from 'react';
import SomeComponent from './SomeComponent.tsx';
import renderConnected from 'utils/renderConnected';

describe('Test SomeComponent', () => {
  const initialState = {
    name: "your name"
  };

  it('should show the name properly', () => {
    const props = {
      // ... pass any additional prop
    };
    renderConnected(<SomeComponent {...props} />, { initialState });
    expect(screen.getByText("your name")).toBeDefined();
  });
});

场景2:使用UI库和自定义主题

但问题并没有就此结束。很多时候,我们需要用许多类型的提供者包装我们的根组件。

其中一个例子是material-ui或styled-components的ThemeProvider。

代码语言:javascript
复制
<ThemeProvider theme={theme}>
  <CustomCheckbox defaultChecked />
</ThemeProvider>

现在,如果要测试组件功能,该功能使用提供者传递的值,那么测试将失败!

我们可以使用相同的概念来缓解此问题,并用ThemeProvider包装根组件。

为了缓解这个问题,让我们调整renderConnected函数,将组件包装在ThemeProvider中。

代码语言:javascript
复制
import { render, RenderOptions } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { initialState as reducerInitialState, reducer, store } from 'reducer';

// ---- 注意这里
import { ThemeProvider } from 'styled-components';
import { customTheme } from './customTheme'

type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducer, initialState),
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider theme={customTheme}> // .................... 注意这里......................... <-
      <Provider store={store

}>
        {children}
      </Provider>
    </ThemeProvider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;

现在,我们可以传递任何组件,测试将通过。

场景3:使用React Router进行测试

将任何操作完成后导航到新路由是一种非常常见的做法。

比如说,你希望在登录成功后将用户重定向到首页。

我们该怎么做呢?

我们可以利用react-router提供的MemoryRouter。我们可以传递URL路径并测试我们的组件。

我们稍后将看到它是如何工作的,但首先让我们将其添加到代码中!

修改后的renderConnected版本将如下所示:

代码语言:javascript
复制
// .. 与之前相同
import { MemoryRouter } from 'react-router-dom';

type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;

  route?: string; // 新加入的!
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducer, initialState),
    route = '/', // 新增加
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider theme={customTheme}>
      <Provider store={store}>
        <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter> // 新增加!
      </Provider>
    </ThemeProvider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;

请注意,我们现在将一个新的参数route传递给函数。我们还将我们的children用react-router提供的MemoryRouter包装起来。

测试导航

比如说,你正在测试一个FirstPage,点击按钮后导航到另一页SecondPage。你想测试这种行为。

但问题是SecondComponent尚未挂载....对吗?

一种方法是模拟react-router的useNavigation或history对象。

但有一种更简单的方法。我们将使用react-router-dom的Router来为第二个URL路径挂载一个虚拟组件,并确保它显示在画面中。

代码语言:javascript
复制
it('Test navigation' , () => {
  const ui = renderConnected(
    <>
      <FirstPage />
      <Route path="/second-page">Second Page</Route>
    </>,
    { initialState }
  );

  expect(screen.queryByText('Second Page')).toBeNull();
  expect(screen.getByText(/First Page/)).toBeDefined();

  // 执行操作
  const button = utils.getByText(/Submit/);
  fireEvent.click(button);

  // 此时应该发生导航,我们应该在第二页上
  expect(screen.getByText(/Second Page/)).toBeDefined();
  expect(screen.queryByText(/First Page/)).toBeNull();

})

现在,你的测试将通过,并且你将会看到那些甜美的绿色复选框!

通过这些高级测试技巧,你可以更全面地测试你的React应用程序,覆盖各种场景和组件。这有助于确保应用程序的质量和稳定性。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么这么做?
  • 测试概述 - React
  • 测试场景
    • 场景1:测试Redux连接的组件
      • 场景2:使用UI库和自定义主题
        • 场景3:使用React Router进行测试
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档