@xstate/test

XState test utilities

Usage no npm install needed!

<script type="module">
  import xstateTest from 'https://cdn.skypack.dev/@xstate/test';
</script>

README

@xstate/test

This package contains utilities for facilitating model-based testing for any software.

Talk

Write Fewer Tests! From Automation to Autogeneration at React Rally 2019 (🎥 Video)

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):
// ...

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();
  });
});