# 测试机

¥Testing Machines

这些 XState v4 文档不再维护

XState v5 现已推出!阅读有关 XState v5 的更多信息 (opens new window)

¥XState v5 is out now! Read more about XState v5 (opens new window)

🆕 在我们的新文档中查找有关 使用 XState 进行测试 (opens new window) 的更多信息。

¥🆕 Find more about testing using XState (opens new window) in our new docs.

一般来说,测试状态机和状态图应该通过测试机器的整体行为来完成;那是:

¥In general, testing state machines and statecharts should be done by testing the overall behavior of the machine; that is:

给定当前状态,当发生某些事件序列时,被测系统应处于特定状态和/或呈现特定输出。

¥Given a current state, when some sequence of events occurs, the system under test should be in a certain state and/or exhibit a specific output.

这遵循 行为驱动开发(BDD) (opens new window)黑盒测试 (opens new window) 策略。不应直接测试机器的内部工作情况;相反,应该测试观察到的行为。这使得测试机器比单元测试更接近集成或端到端(E2E)测试。

¥This follows behavior-driven development (BDD) (opens new window) and black-box testing (opens new window) strategies. The internal workings of a machine should not be directly tested; rather, the observed behavior should be tested instead. This makes testing machines closer to integration or end-to-end (E2E) tests than unit tests.

# 测试纯逻辑

¥Testing pure logic

如果你不想测试副作用(例如执行操作或调用参与者),而是想测试纯逻辑,则可以使用 machine.transition(...) 函数来断言在给定初始状态和事件的情况下达到了特定状态:

¥If you do not want to test side-effects, such as executing actions or invoking actors, and want to instead test pure logic, the machine.transition(...) function can be used to assert that a specific state is reached given an initial state and an event:

import { lightMachine } from '../path/to/lightMachine';

it('should reach "yellow" given "green" when the "TIMER" event occurs', () => {
  const expectedValue = 'yellow'; // the expected state value

  const actualState = lightMachine.transition('green', { type: 'TIMER' });

  expect(actualState.matches(expectedValue)).toBeTruthy();
});

# 测试服务

¥Testing services

给定初始状态和事件序列,可以通过断言服务最终达到预期状态来测试服务的行为和输出:

¥The behavior and output of services can be tested by asserting that it eventually reaches an expected state, given an initial state and a sequence of events:

import { fetchMachine } from '../path/to/fetchMachine';

it('should eventually reach "success"', (done) => {
  const fetchService = interpret(fetchMachine).onTransition((state) => {
    // this is where you expect the state to eventually
    // be reached
    if (state.matches('success')) {
      done();
    }
  });

  fetchService.start();

  // send zero or more events to the service that should
  // cause it to eventually reach its expected state
  fetchService.send({ type: 'FETCH', id: 42 });
});

提示

请记住,大多数测试框架都有默认超时,并且异步测试预计会在该超时之前完成。如果需要 (例如,jest.setTimeout(timeout) (opens new window)),请为运行时间较长的测试配置超时。

¥Keep in mind that most testing frameworks have a default timeout, and the async tests are expected to finish before that timeout. Configure the timeout if necessary (e.g., jest.setTimeout(timeout) (opens new window)) for longer-running tests.

# 模拟副作用

¥Mocking effects

由于操作和调用/生成 Actor 是副作用,因此可能不希望在测试环境中执行它们。你可以使用 machine.withConfig(...) 选项更改某些操作的实现细节:

¥Since actions and invoking/spawning actors are side-effects, it might be undesirable to execute them in a testing environment. You can use the machine.withConfig(...) option to change the implementation details of certain actions:

import { fetchMachine } from '../path/to/fetchMachine';

it('should eventually reach "success"', (done) => {
  let userAlerted = false;

  const mockFetchMachine = fetchMachine.withConfig({
    services: {
      fetchFromAPI: (_, event) =>
        new Promise((resolve) => {
          setTimeout(() => {
            resolve({ id: event.id });
          }, 50);
        })
    },
    actions: {
      alertUser: () => {
        // set a flag instead of executing the original action
        userAlerted = true;
      }
    }
  });

  const fetchService = interpret(mockFetchMachine).onTransition((state) => {
    if (state.matches('success')) {
      // assert that effects were executed
      expect(userAlerted).toBeTruthy();
      done();
    }
  });

  fetchService.start();

  fetchService.send({ type: 'FETCH', id: 42 });
});