# @xstate/test

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

# Quick Start

  1. Install xstate and @xstate/test:
npm install xstate @xstate/test
  1. 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. 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?)

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

Argument Type Description
machine StateMachine The machine used to create the abstract model.
options? TestModelOptions Options to customize the abstract model

Returns

A TestModel instance.

Methods

# model.withEvents(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 (function): Function that executes the events. It is given two arguments:
    • testContext (any): any contextual testing data
    • event (EventObject): the event sent by the testing model
  • 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

Argument Type Description
filter function Takes in the state and returns true if the state should be traversed, or false if traversal should stop.

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

Argument Type Description
filter function Takes in the state and returns true if the state should be traversed, or false if traversal should stop.

# testModel.getPlanFromEvents(events, options)

Argument Type Description
events EventObject[] The sequence of events to create the plan
options { target: string } An object with a target property that should match the target state of the events

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

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

Argument Type Description
filter function Takes in each stateNode and returns true if that state node should have been covered.
// Only test coverage for state nodes with a `.meta` property defined:

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

# testPlan.description

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

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

# testPath.test(testContext)

Executes each step in testPath.segments by:

  1. Verifying that the SUT is in segment.state
  2. Executing the event for segment.event

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

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.