React 测试笔记 03 - 测试 Redux 中 Reducer 状态变化

React 测试笔记 03 - 测试 Redux 中 Reducer 状态变化

这段时间都在重构代码,把本来奇奇怪怪(singleton)的实现改成用 redux 的实现,然后就突然想到......即然 redux 的改变不涉及到 UI 的改变,那么是不是说可以单独写 redux 的测试......?

找了一下资料,发现比想象中的简单很多,所以就稍微记一下

要求简述

这里的案例主要就是通过用户权限,然后 map 对应的页面的读写(全套为增删改查)权限,基本逻辑是这样的:

  • 权限分为 read_only、full、A、B、C(A/B/C 为抽象的权限,基本上说对应一下用户可以获得不同分类的权限)

  • 每个权限对应着每个页面的读写操作

    如 full 代表着用户对所有页面都有读写操作,read_only 相反

    A/B/C 给予用户 页面 A/页面 B/页面 C 的读写权限

  • 用户可以有不同的权限

  • 更高的权限将会复写较低的权限

    理论上来说这是不可能发生的事情,不过假设用户同时有 read_only 和 full,那么 full 将会复写 read_only 的权限

    比较通常发生的是用户可能会有 A/B/C 这样的权限,但是其中对某些页面会有覆盖操作,如权限 B 给予 B 页面读写操作,权限 C 可能对 B 页面有改的权限,但是没有增删

  • 当前 redux 状态只负责授予权限,具体权限的处理则是在 路由/component 处进行处理

实现简述

具体的实现就是 dispatch event 实现,权限的 map 则是通过 {page: permission} 的方式进行存储,至于要求说是取最高权限,因此 permission 的实现方式采用数字,在 redux 中使用 Math.max() 的方式取最大值

需要注意的是,这里在实现的时候可能会出现 type casting 的异常,如 0 | 1 | 2 | 3 is not compatible with number 之类的,我的实现方式是用 Math.max() as (typeof T)[keyof typeof T] 的方式进行一个转型

当然,cv 太多的话还是建议抽一个函数取实现

这样的话通过 roles.filter(role => role in SOME_CONST) 中可以获取需要 map 的权限,再遍历数组更新权限即可

测试实现

首先我先 mock 了一下整个 app component:

ts 复制代码
jest.mock('../../App', () => ({}));

这一步是假设 app component 已经渲染完毕了,因为其他的 redux 对一些 util------有可能是异步的操作------有一些依赖的关系,然后目前 jest 没有找到这些 util,因此就会抛出找不到 util 的异常,所以这里先 mock 一下虚构的 app,让 jest 知道内部的 util 实现不重要,最终结果就是所有的组件已经正常渲染完毕

其主要原因也是我们的项目有一些 context wrapper 了:

tsx 复制代码
// redux
<Provider store={store}>
  {/* async code,从 API 处获得权限,再传到 redux 中 */}
  {/* 如果不 mock,从这里就会报错 */}
  <AccessContext>
    <OtherContext>
      <App />
    </OtherContext>
  </AccessContext>
</Provider>

之后的操作非常的简单,jest 已经默认 app 可以正确渲染,因此这里只需要出发 RTK 的状态变化,并且检查状态变化即可,如:

ts 复制代码
import {
  ISliceType,
  sliceReducer,
  exportedAction,
} from '../../store/slices/state/keycloak';

jest.mock('../../App', () => ({}));

// 这里可以实现一些状态用来方便 test,而不用手写一堆代码
const initialState: ISliceType = {}; // AKA readOnly
const fullAccess: ISliceType = {};
const aAccess: Partial<ISliceType> = {};
const bAccess: Partial<ISliceType> = {};

const validationHelper = (
  grantedPerm: ISliceType,
  checkAccess: Partial<ISliceType>
): boolean => {
  // do some check
  // 也许可以遍历 checkAccess 的 key,保证 grantedPerm[key] >= checkAccess[key] 这种

  return true;
};

describe('test user has readOnly access', () => {
  test('initial state is set to readOnly', () => {
    expect(sliceReducer(undefined, { type: undefined })).toEqual(initialState);
  });

  test('user only has readOnly access', () => {
    const payload = { role: ['readonly'] };
    expect(sliceReducer(initialState, exportedAction(payload))).toEqual(
      initialState
    );
  });

  test('user has multiple role', () => {
    const payload = { role: ['readonly', 'aAccess'] };

    const grantedPerm = sliceReducer(
      initialState,
      exportedAction(payload)
    ).permission;

    expect(validationHelper(grantedPerm, initialState)).toBeTruthy();
    expect(validationHelper(grantedPerm, aAccess)).toBeTruthy();
    expect(validationHelper(grantedPerm, bAccess)).toBeFalsy();
  });
});

这里检查了三种状态:

  • undefined 相当于触发 Reducer 的初始化,因此返回初始状态

    因为初始状态直接声明了,所以可以直接用 toEqual 去测试

  • 测试只读状态

    这里假设返回的状态依旧比较简单,因此仍旧可以使用 toEqual 去和整个状态测试

  • 测试更复杂的状态

    这个就是用的第三个方法,主要是用 toBeTruthy()toBeFalsy() 测试

    假设说页面有十几二十个,然后有四五组权限,全都手写的话会引入更多的 human error,这个时候就可以考虑将一些常量抽出来

    比如说 a 权限对应的 {page: access} 可以单独抽出来做一个变量------这个部分是可以实现、测试公用的,然后再写一个 helper function------这里可以用于测试给予的权限是否大于等于变量中的权限,而没有赋值的权限是否小于等于当前权限,这个根据具体需求具体实现

    我这里的 helper 直接返回了 boolean,使用上面列举的两个方式去测试,不过实际上还可以搭配其他的 Modifiers 和 Matchers 去测试,不仅仅是用 truthy/falsy

    这个不是必须的,因为测试有些情况下就是会 cv 一些代码,而且一些复杂的情况下,仅返回 boolean 的 helper 也许不是这么的好用,那么最差情况下就是得一遍遍手写,然后通过 npm test 去运行测试结果

reference

  • Writing Tests

    这里只用了单元测试 reducer 的部分

  • Expect

    下面的 reference 列举了 Modifiers 和 Matchers

相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_7482402510 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar10 小时前
纯前端实现更新检测
开发语言·前端·javascript