https://github.com/smartive/xstate-test-toolbox
This package contains the helper createTestPlans which can be used with `xstate` and `@xstate/test`.
https://github.com/smartive/xstate-test-toolbox
guards testing tests xstate
Last synced: about 1 year ago
JSON representation
This package contains the helper createTestPlans which can be used with `xstate` and `@xstate/test`.
- Host: GitHub
- URL: https://github.com/smartive/xstate-test-toolbox
- Owner: smartive
- License: mit
- Created: 2020-12-15T16:52:39.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2025-04-03T02:44:50.000Z (about 1 year ago)
- Last Synced: 2025-04-05T18:12:35.624Z (about 1 year ago)
- Topics: guards, testing, tests, xstate
- Language: TypeScript
- Homepage: https://blog.smartive.ch/3-problems-we-encountered-using-xstate-test-with-testcafe-and-their-solutions-492c237b5f85
- Size: 616 KB
- Stars: 13
- Watchers: 5
- Forks: 0
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @smartive/xstate-test-toolbox
This package contains the helper `createTestPlans` which can be used with xstate and @xstate/test.
## `createTestPlans`
This function adds the `meta`-property to every state and a test if it is defined within `tests`. (see in example) Beside that it generates all possible test simple path plans for all possible combinations of your guards.
⚠️ Attention: Your statechart must consist of only string references to guards, actions and services otherwise the testing will break.
### Example
```Typescript
// The following snippet does not include all needed imports and code it is intended
// to give you a starting point and an idea how the `createTestPlans`-function can be used.
import { createTestPlans, StatesTestFunctions } from '@smartive/xstate-test-toolbox';
import { FetchInterceptor, mockHeaders, mockResponse, RequestCallCountMock } from '@smartive/testcafe-utils';
import { TestEventsConfig } from '@xstate/test/lib/types';
import { RequestMock } from 'testcafe';
import { Context, machine } from './machine-under-test';
// ...
type TestContext = {
t: TestController,
plan: string,
path: string
};
const fetchInterceptor = new FetchInterceptor({
fetchPeople: /.+swapi\.dev.+\/people\/$/,
fetchMore: /.+swapi\.dev.+\/people\/\?page=.+/,
searchPeople: /.+swapi\.dev.+\/people\/\?search=.+/,
});
const getRequestMocks = (plan: string, path: string): object[] => {
const peopleUrl = /.+swapi\.dev.+\/people\/.*/
if (
plan.includes('NoResults') ||
(plan.includes('Searching') && path.includes('NoResults'))
) {
return [
RequestMock()
.onRequestTo(peopleUrl)
.respond(empty, 200, mockHeaders),
];
}
if (plan.includes('Error')) {
switch (path) {
case 'Pending → error.platform.fetchPeople':
return [
RequestMock().onRequestTo(peopleUrl).respond({}, 400, mockHeaders),
];
case 'Pending → done.invoke.fetchPeople → Idle → END_REACHED → LoadingMore → error.platform.fetchMore':
return [
new RequestCallCountMock(peopleUrl, [
{ body: mockResponse(peoples) },
{ body: mockResponse({}, 400) },
]),
];
case 'Pending → done.invoke.fetchPeople → NoResults → QUERY_DISPATCHED → Searching → error.platform.searchPeople':
return [
RequestMock()
.onRequestTo(fetchInterceptor.interceptUrls.searchPeople)
.respond({}, 400, mockHeaders),
RequestMock()
.onRequestTo(peopleUrl)
.respond(empty, 200, mockHeaders),
];
case 'Pending → done.invoke.fetchPeople → Idle → QUERY_DISPATCHED → Searching → error.platform.searchPeople':
return [
RequestMock()
.onRequestTo(fetchInterceptor.interceptUrls.searchPeople)
.respond({}, 400, mockHeaders),
];
}
}
return [];
};
const tests: StatesTestFunctions = {
Pending: ({ t }) => t.expect(page.spinner.exists).ok(),
Idle: ({ t }) => t.expect(page.listItem.count).gt(0),
LoadingMore: ({ t }) => t.expect(page.listItem.count).gt(1).expect(page.spinner.exists).ok(),
Error: ({ t }) => t.expect(page.error.exists).ok(),
NoResults: ({ t }) => t.expect(page.notify.exists).ok(),
Searching: ({ t }) => t.expect(page.search.value).contains('luke').expect(page.spinner.exists).ok(),
};
const testEvents: TestEventsConfig = {
END_REACHED: ({ t }) => t.hover(page.listItem.nth(9), { speed: 0.8 }),
QUERY_DISPATCHED: ({ t }) => t.typeText(page.search, 'luke', { speed: 0.8 }),
'done.invoke.fetchPeople': fetchInterceptor.resolve('fetchPeople'),
'error.platform.fetchPeople': fetchInterceptor.resolve('fetchPeople'),
'error.platform.fetchMore': fetchInterceptor.resolve('fetchMore'),
'error.platform.searchPeople': fetchInterceptor.resolve('searchPeople'),
};
createTestPlans({
machine,
tests,
testEvents,
// add logLevel: LogLevel.INFO for some output which plans/paths are generated
}).forEach(
({ description: plan, paths }) => {
fixture(plan).page(`http://localhost:3000/peoples`);
paths.forEach(({ test: run, description: path }) => {
test
.clientScripts([fetchInterceptor.clientScript()])
.requestHooks(getRequestMocks(plan, path))(`via ${path} ⬏`, (t) => run({ plan, path, t }));
});
}
);
```