我是这样在 React 中实践 TDD 编程的

简介: 我是这样在 React 中实践 TDD 编程的

Redux中编写测试听起来肯定有悖直觉。如果您使用了Redux,它可能看起来更加复杂。

然而,在添加功能之前编写测试有助于编写更好的代码,因为你预先考虑了将使用的设计模式、体系结构和变量的名称。

我们正在构建一个用户管理仪表板。基本上,使用Redux,我们想执行CRUD操作。

用户可以:

  • 创建用户
  • 更新用户
  • 删除用户
  • 获取用户或用户列表

这个小项目中的用户将有四个属性:

  • id\name\username\email

为了简单起见,我们不编写UI代码。我们将主要关注于创建一个测试环境,编写测试,并确保我们能够处理我们想要的内容

开始

首先,创建一个简单的React项目。

yarn create react-app react-redux-test-driven-development

一旦创建了项目,通过运行项目来确保一切正常。

cd react-redux-test-driven-development
yarn start

接下来,我们希望安装redux包和一个mock适配器。mock适配器将帮助我们模拟服务器上的请求。

yarn add @reduxjs/toolkit axios-mock-adapter axios

测试 mock 数据

src目录中,创建一个名为utils的新目录。然后,创建一个名为tests.data.js的文件。

该文件将包含以下方法和变量:

  • mockNetWorkResponse:在默认实例上创建mock适配器,并模拟到所需端点的任何GET或POST请求;
  • getCreateUserResponse:返回/user/上POST请求的响应;
  • getUserListResponse: 返回对/user/的GET请求的响应。

让我们来写这些方法:

import axios from "axios";
import MockAdapter from "axios-mock-adapter";
const getCreateUserResponse = {
  id: 3,
  name: "Clementine Bauch",
  username: "Samantha",
  email: "Nathan@yesenia.net"
};
const getUserListResponse = [
  {
    id: 1,
    name: "Leanne Graham",
    username: "Bret",
    email: "Sincere@april.biz"
  },
  {
    id: 2,
    name: "Ervin Howell",
    username: "Antonette",
    email: "ervin@april.biz"
  },
];
// Adding mock network response that is used in tests
const mockNetWorkResponse = () => {
  const mock = new MockAdapter(axios);
  mock.onGet(`/users/`).reply(200, getUserListResponse);
  mock.onPost(`/users/`).reply(200, getCreateUserResponse);
};
export {
  mockNetWorkResponse,
  getCreateUserResponse,
  getUserListResponse,
};

太棒了!准备好mock适配器后,我们就可以专注于初始化存储和并编写测试了。

编写测试

这是最有趣的部分。让我们开始TDD

首先,让我们创建并配置存储。在src目录中,创建一个名为index.js的新目录。在这个文件中,初始化存储。

import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";
const rootReducer = combineReducers({
  // Adding the reducers
});
export const store = configureStore({
  reducer: rootReducer,
});

编写 userSlice

“slice”是应用程序中单个特性的Redux reducer逻辑和动作的集合,通常定义在单个文件中。userSlice将有actionsreducer来执行CRUD操作。

slice的默认状态应该是一个空数组,毕竟,我们处理的是用户。

让我们通过编写一个测试:

src/store中创建一个名为slices的新目录。

在这个目录中,添加一个名为user.test.js的文件。这个文件将包含我们将为userSlice编写的测试。

第一个测试是确保存储是空的或未定义的。初始状态可能是这样的:

const initialState = {
  users: [],
  loading: false,
  error: null
};

让我们尝试写一下这个测试:

  • 测试初始 State。在user.test.js文件中,添加以下测试:
import reducer, {
    initialState,
  } from "./user";
  /**
   * Testing the initial state
   */
  test("Should return initial state", () => {
    expect(
      reducer(undefined, {
        type: undefined,
      })
    ).toEqual(initialState);
  });

现在运行yarn test命令。测试将失败❌

完全正常。我们还没有定义userSlice、reducer初始状态

slice目录中,创建一个名为user.js的文件。

export const initialState = {
  users: [],
  loading: false,
  error: null
};
export const userSlice = createSlice({
  name: "users",
  initialState: initialState,
  extraReducers: () => {
  },
});
export default userSlice.reducer;

另外,在store/index.js中注册 slice reducer

import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";
import { userSlice } from "./slices/user";
const rootReducer = combineReducers({
  users: userSlice.reducer,
});
export const store = configureStore({
  reducer: rootReducer,
});

然后再次运行测试✅

  • 测试用户创建:为此,我们需要编写一个thunkthunk是一个函数,它以storedispatch方法作为参数,然后在API或副作用完成后使用它来dispatch同步操作。

首先,让我们为这个特性编写测试。

import reducer, {
    initialState,
    addUser
  } from "./user";
  import {
    mockNetWorkResponse,
    getCreateUserResponse,
  } from "../../utils/tests.data";
 /**
   * Testing the createUser thunk
   */
  describe("Create a new user", () => {
    beforeAll(() => {
      mockNetWorkResponse();
    });
    it("Should be able to create a new user", async () => {
      // Saving previous state
      const previousState = store.getState().users;
      const previousUsers = [...previousState.users];
      previousUsers.push(getCreateUserResponse);
      // Dispatching the action
      const result = await store.dispatch(addUser(getCreateUserResponse));
      const user = result.payload;
      expect(result.type).toBe("users/addUser/fulfilled");
      expect(user).toEqual(getCreateUserResponse);
      const state = store.getState().users;
      expect(state.users).toEqual(previousUsers);
    });

在这个测试中,我们是:

  • 在进行更新之前,保存以前的状态并将users属性修改为预期状态。这将有助于我们比较下一个状态。
  • dispatch一个action,并确保它已完成,并比较预期状态和实际状态。
    同样,测试将失败。让我们为创建用户特性添加thunkreducer
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const addUser = createAsyncThunk("users/addUser", async (user) => {
  const res = await axios.post(`/users/`, user);
  return res.data;
});
export const initialState = {
  users: [],
  loading: false,
  error: null
};
export const userSlice = createSlice({
  name: "users",
  initialState: initialState,
  extraReducers: () => {
    /*
     * addUser Cases
     */
    builder.addCase(addUser.pending, (state) => {
      state.loading = true;
    });
    builder.addCase(addUser.rejected, (state, action) => {
      state.loading = false;
      state.error = action.error.message || "Something went wrong";
    });
    builder.addCase(addUser.fulfilled, (state, action) => {
      state.loading = true;
      state.users.push(action.payload);
    });
  },
});
export default userSlice.reducer;
export { addUser };

并再次运行测试,它应该会通过。✅

编写测试获取用户列表

首先,让我们为这个特性编写测试。

import reducer, {
    initialState,
    addUser,
    fetchUsers
  } from "./user";
  import {
    mockNetWorkResponse,
    getCreateUserResponse,
    getUserListResponse
  } from "../../utils/tests.data";
...
  /**
   * Testing the fetchUsers thunk
   */
  describe("List all users", () => {
    beforeAll(() => {
      mockNetWorkResponse();
    });
    it("Shoudl be able to fetch the user list", async () => {
      const result = await store.dispatch(fetchUsers());
      const users = result.payload;
      expect(result.type).toBe("users/fetchUsers/fulfilled");
      expect(users).toEqual(getUserListResponse);
      const state = store.getState().users;
      expect(state.users).toEqual(getUserListResponse);
    });
  });

这样测试会失败。

让我们加上reducerthunk

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const fetchUsers = createAsyncThunk(
  "users/fetchUsers",
  async () => {
    const response = await axios.get(`/users/`);
    return response.data;
  }
);
const addUser = createAsyncThunk("users/addUser", async (user) => {
  const res = await axios.post(`/users/`, user);
  return res.data;
});
export const initialState = {
  users: [],
  loading: false,
  error: null
};
export const userSlice = createSlice({
  name: "users",
  initialState: initialState,
  extraReducers: () => {
    /*
     * addUser Cases
     */
    builder.addCase(addUser.pending, (state) => {
      state.loading = true;
    });
    builder.addCase(addUser.rejected, (state, action) => {
      state.loading = false;
      state.error = action.error.message || "Something went wrong";
    });
    builder.addCase(addUser.fulfilled, (state, action) => {
      state.loading = true;
      state.users.push(action.payload);
    });
    /*
     * fetchUsers Cases
     */
    builder.addCase(fetchUsers.pending, (state) => {
      state.loading = true;
    });
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      state.loading = false;
      state.users = action.payload;
    });
    builder.addCase(fetchUsers.rejected, (state) => {
      state.loading = false;
    });
  },
});
export default userSlice.reducer;
export { addUser, fetchUsers };

测试通过。✅

太棒了!我们刚刚使用Redux、thunk和axios mock编写了一些测试🤩

对你来说有点挑战吗?添加诸如删除用户、修改以及检索用户等功能。

结论

在本文中,我们快速介绍了使用ReduxTDD。如果你希望使用TDD编写React组件,你可以查看我写的这篇文章。


相关文章
|
2月前
|
前端开发 JavaScript
深入理解并实践React Hooks —— useEffect与useState
深入理解并实践React Hooks —— useEffect与useState
143 1
|
6月前
|
前端开发 算法 API
Multi-Agent实践第4期:智能体的“想”与“做”-ReAct Agent
本期文章,我们将向大家展示如何使用AgentScope内置的ReAct智能体解决更为复杂的问题。
|
6月前
|
前端开发 开发者
探索前端框架的新趋势:React Hooks的应用与实践
本文将深入探讨前端开发中的新趋势,重点介绍React Hooks的应用与实践。通过学习和使用React Hooks,开发者可以更高效地构建可维护、可扩展的前端应用程序。本文将详细介绍React Hooks的原理、优势以及如何在实际项目中运用Hooks来提高开发效率并改善代码结构。无论你是刚入门前端开发还是经验丰富的工程师,本文都将对你有所启发。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
21天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
2月前
|
前端开发 JavaScript UED
深入React Hooks与性能优化实践
深入React Hooks与性能优化实践
46 0
|
3月前
|
前端开发 JavaScript
React面向组件编程(二)
【8月更文挑战第14天】React面向组件编程(二)
27 0
React面向组件编程(二)
|
3月前
|
移动开发 前端开发 JavaScript
使用React Native进行跨平台移动开发:技术探索与实践
【8月更文挑战第10天】React Native以其跨平台、高性能、易学习等优势,在移动开发领域取得了显著的成果。通过合理使用React Native,开发者可以更加高效地开发出高质量、低成本的移动应用。然而,在享受React Native带来的便利的同时,我们也需要关注其潜在的挑战和限制,并通过不断学习和实践来提升我们的开发能力。
|
3月前
|
开发者 安全 UED
JSF事件监听器:解锁动态界面的秘密武器,你真的知道如何驾驭它吗?
【8月更文挑战第31天】在构建动态用户界面时,事件监听器是实现组件间通信和响应用户操作的关键机制。JavaServer Faces (JSF) 提供了完整的事件模型,通过自定义事件监听器扩展组件行为。本文详细介绍如何在 JSF 应用中创建和使用事件监听器,提升应用的交互性和响应能力。
37 0
|
3月前
|
开发者 Java
JSF EL 表达式:乘技术潮流之风,筑简洁开发之梦,触动开发者心弦的强大语言
【8月更文挑战第31天】JavaServer Faces (JSF) 的表达式语言 (EL) 是一种强大的工具,允许开发者在 JSF 页面和后台 bean 间进行简洁高效的数据绑定。本文介绍了 JSF EL 的基本概念及使用技巧,包括访问 bean 属性和方法、数据绑定、内置对象使用、条件判断和循环等,并分享了最佳实践建议,帮助提升开发效率和代码质量。
44 0