# @xstate/test

@xstate/test package (opens new window) 包含有助于任何软件使用 基于模型的测试 (opens new window) 的实用程序。

¥The @xstate/test package (opens new window) contains utilities for facilitating model-based testing (opens new window) for any software.

# 快速开始

¥Quick Start

  1. 安装 xstate@xstate/test

    ¥Install xstate and @xstate/test:

npm install xstate @xstate/test
  1. 创建将用于对被测系统 (SUT) 进行建模的机器:

    ¥Create the machine that will be used to model the system under test (SUT):

import { createMachine } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: 'active'
      }
    },
    active: {
      on: {
        TOGGLE: 'inactive'
      }
    }
  }
});
  1. 为机器中的每个状态添加断言(在本例中,使用 Puppeteer (opens new window)):

    ¥Add assertions for each state in the machine (in this example, using Puppeteer (opens new window)):

// ...

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        /* ... */
      },
      meta: {
        test: async (page) => {
          await page.waitFor('input:checked');
        }
      }
    },
    active: {
      on: {
        /* ... */
      },
      meta: {
        test: async (page) => {
          await page.waitFor('input:not(:checked)');
        }
      }
    }
  }
});
  1. 创建模型:

    ¥Create the model:

import { createMachine } from 'xstate';
import { createModel } from '@xstate/test';

const toggleMachine = createMachine(/* ... */);

const toggleModel = createModel(toggleMachine).withEvents({
  TOGGLE: {
    exec: async (page) => {
      await page.click('input');
    }
  }
});
  1. 创建测试计划并运行覆盖范围的测试:

    ¥Create test plans and run the tests with coverage:

// ...

describe('toggle', () => {
  const testPlans = toggleModel.getShortestPathPlans();

  testPlans.forEach((plan) => {
    describe(plan.description, () => {
      plan.paths.forEach((path) => {
        it(path.description, async () => {
          // do any setup, then...

          await path.test(page);
        });
      });
    });
  });

  it('should have full coverage', () => {
    return toggleModel.testCoverage();
  });
});

# API

# createModel(machine, options?)

根据传入的 machine 创建抽象测试模型。

¥Creates an abstract testing model based on the machine passed in.

争论 类型 描述
machine StateMachine 用于创建抽象模型的机器。
options? TestModelOptions 自定义抽象模型的选项

返回

¥Returns

TestModel 实例。

¥A TestModel instance.

方法

¥Methods

# model.withEvents(eventsMap)

提供每个事件的测试详细信息。eventsMap 中的每个键都是一个对象,其键是事件类型,属性描述每个事件的执行和测试用例:

¥Provides testing details for each event. Each key in eventsMap is an object whose keys are event types and properties describe the execution and test cases for each event:

  • exec(功能):执行事件的函数。它有两个参数:

    ¥exec (function): Function that executes the events. It is given two arguments:

    • testContext(任意):任何上下文测试数据

      ¥testContext (any): any contextual testing data

    • event(事件对象):测试模型发送的事件

      ¥event (EventObject): the event sent by the testing model

  • cases?(事件对象[]):测试模型可以发送的此事件类型的示例事件对象。

    ¥cases? (EventObject[]): the sample event objects for this event type that can be sent by the testing model.

示例

¥Example

const toggleModel = createModel(toggleMachine).withEvents({
  TOGGLE: {
    exec: async (page) => {
      await page.click('input');
    }
  }
});

# testModel.getShortestPathPlans(options?)

根据从测试模型的初始状态到每个其他可到达状态的最短路径,返回一组测试计划。

¥Returns an array of testing plans based on the shortest paths from the test model's initial state to every other reachable state.

选项

¥Options

争论 类型 描述
filter function 如果应该遍历状态,则接受 state 并返回 true;如果应该停止遍历,则返回 false

这对于防止无限遍历和堆栈溢出错误很有用:

¥This is useful for preventing infinite traversals and stack overflow errors:

const todosModel = createModel(todosMachine).withEvents({
  /* ... */
});

const plans = todosModel.getShortestPathPlans({
  // Tell the algorithm to limit state/event adjacency map to states
  // that have less than 5 todos
  filter: (state) => state.context.todos.length < 5
});

# testModel.getSimplePathPlans(options?)

基于从测试模型的初始状态到每个其他可到达状态的简单路径,返回一组测试计划。

¥Returns an array of testing plans based on the simple paths from the test model's initial state to every other reachable state.

选项

¥Options

争论 类型 描述
filter function 如果应该遍历状态,则接受 state 并返回 true;如果应该停止遍历,则返回 false

# testModel.getPlanFromEvents(events, options)

争论 类型 描述
events EventObject[] 创建计划的事件顺序
options { target: string } 具有 target 属性的对象,该属性应与事件的目标状态匹配

返回一个具有单个测试计划的数组,该测试计划具有从 events 生成的单个路径。

¥Returns an array with a single testing plan with a single path generated from the events.

如果最后输入的状态与 options.target 不匹配,则会引发错误。

¥Throws an error if the last entered state does not match the options.target.

# testModel.testCoverage(options?)

测试在执行的测试中是否覆盖(遍历)所有状态节点。

¥Tests that all state nodes were covered (traversed) in the executed tests.

选项

¥***Options***

争论 类型 描述
filter function 如果该状态节点应该被覆盖,则接收每个 stateNode 并返回 true
// Only test coverage for state nodes with a `.meta` property defined:

testModel.testCoverage({
  filter: (stateNode) => !!stateNode.meta
});

# testPlan.description

测试计划的字符串描述,描述达到 testPlan.state 的目标。

¥The string description of the testing plan, describing the goal of reaching the testPlan.state.

# testPlan.paths

从测试模型的初始状态到每个其他可到达状态的测试路径。

¥The testing paths to get from the test model's initial state to every other reachable state.

# testPath.description

测试路径的字符串描述,描述将到达 testPath.state 的事件序列。

¥The string description of the testing path, describing a sequence of events that will reach the testPath.state.

# testPath.test(testContext)

通过以下方式执行 testPath.segments 中的每个步骤:

¥Executes each step in testPath.segments by:

  1. 验证 SUT 是否位于 segment.state

    ¥Verifying that the SUT is in segment.state

  2. 执行 segment.event 的事件

    ¥Executing the event for segment.event

最后,验证 SUT 是否位于目标 testPath.state 中。

¥And finally, verifying that the SUT is in the target testPath.state.

注意:如果你的模型具有嵌套状态,则在验证 SUT 是否处于该嵌套状态时,也会执行该嵌套状态的每个父状态的 meta.test 方法。

¥NOTE: If your model has nested states, the meta.test method for each parent state of that nested state is also executed when verifying that the SUT is in that nested state.