用Jest对Vuex模块进行单元测试的教程

189 阅读5分钟

如果你正在构建一个中等规模的SPA,你有可能会遇到想要更好地处理你的Vue组件的状态的情况。

在任何应用程序中,多个组件都依赖于同一块状态。让我们想象一下,来自不同组件的多个动作都想突变同一个状态。为了克服这些挑战,Vuex帮助我们维护整个应用程序的状态。

在这篇文章中,我将指导你在TypeScript中实现一个Vuex模块,然后用Jest对其进行单元测试。本教程的完整代码可在vuex-test的GitHub仓库中找到,请随时分叉。让我们开始吧!

什么是Vuex?

Vuex是Vue应用程序的状态管理模式和库,允许你在你的应用程序中使用集中的状态管理,帮助你利用类似Flux的架构。Vuex存储包含四个核心概念:

  1. 状态
  2. 获取器
  3. 变异
  4. 行动

状态对象包含你想在存储中拥有的数据,包括你所有的应用程序级别的状态,作为单一的真理来源。状态中定义的属性可以是任何数据类型,包括字符串、数字、对象或数组。

如果你想有一个基于存储状态的派生状态,例如,计算项目列表,过滤集合,或在其他模块或组件中使用相同的派生状态集,你可以定义获取器

另一方面,突变是我们改变状态的唯一途径。突变总是同步的,而且有效载荷是可选的。你可以通过提交来调用突变,即MUTATION_NAMEpayload 。我们总是建议从动作中调用突变。

动作可以执行异步操作并提交突变。动作处理程序接收一个上下文对象,该对象在存储实例上暴露了相同的方法或属性集。

你可以使用context.getterscontext.state 来获取状态,并使用context.commit 来调用突变。你可以使用action-namepayload 来调用动作处理程序,它们可以从商店内的其他动作中调用:

Vuex Modules Diagram

Vuex架构

创建一个Vuex模块

随着你的应用程序规模的增加,你的商店会变得臃肿。为了防止这种情况,Vuex允许你将商店分割成模块。每个模块可以包含自己的状态、getters、突变和动作。

作为一个例子,让我们创建一个管理待办事项列表的应用程序。首先,创建一个新的待办事项操作模块,它负责获取所有的待办事项,并根据需要更新状态。

我们的目标是为中到大规模的应用建立模块,因此,最好将突变类型、被称为函数的操作和模块的实现分成不同的文件:

  • mutation-types.ts:包含所有的函数名称
  • actions.ts:负责所有的异步操作
  • index.ts:模块的实现
import { IToDo } from '@/types/todo';
import {Module, VuexModule, Mutation, Action} from 'vuex-module-decorators';
import TodoActions from './actions';
import * as mutationTypes from './mutation-types';

@Module({namespaced: true, name: "Todos"})
export class ToDoModule extends VuexModule {
  todos:Array<IToDo> = [];
  loading = false;
  get completedTodos(){
    return this.todos.filter((todo:IToDo)=> todo.completed);
  }
  @Mutation
  [mutationTypes.ON_FETCH_TODOS_STARTED]() {
    this.loading = true;
  }
  @Mutation
  \[mutationTypes.ON_FETCH_TODOS_SUCCESS\](data: Array<IToDo>) {
    this.loading = false;
    this.todos = data;
  }
  @Mutation
  [mutationTypes.ON_FETCH_TODOS_FAILED]() {
    this.loading = false;
    this.todos = [];
  }

  @Action({rawError: true})
  public async fetchTodos():Promise<void> {
      try {
          this.context.commit(mutationTypes.ON_FETCH_TODOS_STARTED);
          const response: Array<IToDo> = await TodoActions.fetchTodos();
          this.context.commit(mutationTypes.ON_FETCH_TODOS_SUCCESS, response);
        } catch (error) {
          this.context.commit(mutationTypes.ON_FETCH_TODOS_FAILED);
        }
  }

}

上面的代码片段包含以下实现:

  • fetchTodos Action:从REST API中获取待办事项,并提交突变的内容
  • ON_FETCH_TODOS_STARTED 突变:更新 状态属性loading
  • ON_FETCH_TODOS_SUCCESS 突变:更新 状态数组todos
  • ON_FETCH_TODOS_FAILED 突变:重置todos ,并将 loading更新为false。
  • completedTodos 获取器:只获取已完成的待办事项

初始化测试

我们将使用Jest框架进行单元测试;Jest只是一个JavaScript测试框架,可以很容易地用任何基于节点的包管理器安装,如npm或Yarn。使用Jest有一些优势,例如,Jest测试可以并行运行,包括内置的代码覆盖率,并支持隔离测试、嘲弄和快照测试。

你可以通过创建一个商店,将Vuex附加到Vue,并注册商店来初始化测试。localVue 是范围内的Vue构造函数,我们可以在不影响全局Vue构造函数的情况下改变它。下面的代码片断将初始化商店:

describe('Todos Module', function() {
    let store: any;
    let todosInstance: ToDoModule;

    beforeEach(function() {
      localVue.use(Vuex);
      store = new Vuex.Store({});
      registerStoreModules(store);
      todosInstance = getModule(ToDoModule, store);
    });

    it('should exists', function() {
      expect(todosInstance).toBeDefined();
    });
});

测试动作

todos 模块中,我们创建了fetchTodos 动作,它从REST API中获取数据并使用突变来填充状态。由于REST API是一个外部调用,我们可以使用Jest函数来模拟它,然后验证它是否被调用以及状态是否被更新:

it('fetchTodos action should fill todos state', async function() {
      // arrange
      const todosMocked = todos as Array<IToDo>;
       // act
      jest.spyOn(TodoActions, 'fetchTodos').mockImplementation(
        (): Promise<Array<IToDo>> => {
          return Promise.resolve(todosMocked);
        }
      );
      await todosInstance.fetchTodos();
      // assert
      expect(todosInstance.todos.length >0).toEqual(true);
      expect(TodoActions.fetchTodos).toHaveBeenCalled();
});

测试获取器

Getter函数只是返回状态对象。在我们的例子中,我们有一个getter函数,completedTodos ,它应该返回已经完成的to-do项目。

  it('completedTodos getter should return only completed todos', async function() {
      // arrange
      const completedTodos = todosInstance.completedTodos;
      // assert
      expect(completedTodos.every((todo:IToDo)=> todo.completed)).toEqual(true);
    });

测试突变

正如我们已经知道的,突变是改变状态的唯一途径。我们可以通过发送模拟待办事项并验证状态是否被修改来测试ON_FETCH_TODOS_SUCCESS 突变。

下面的代码片断是针对success 突变的。这也适用于startederror 突变:

it('ON_FETCH_TODOS_SUCCESS mutation should update given todos',  function() {
      // arrange 
      const todosTest = [        {          userId: 13,          id: 12,          title: "Move to new city",          completed: false        },        {          userId: 15,          id: 21,          title: "Finish a novel",          completed: true        },      ];
      // act
      todosInstance.ON_FETCH_TODOS_SUCCESS(todosTest);
      // assert
      expect(todosInstance.todos.length).toEqual(2);
      expect(todosInstance.todos).toEqual(todosTest);
    });

结论

在本教程中,我们通过使用TypeScript和Jest创建和单元测试一个Vuex模块,了解了Vuex。我们涵盖了Vuex存储的四个核心概念,包括状态、获取器、突变和动作。通过Vuex的集中式状态管理,你可以简化你的应用程序,并利用类似Flux的架构优势。

我希望你能学到一些新东西,如果你有任何问题,请务必留言。编码愉快!