@alator21/jobless-machine@0.0.22
Test framework
Jobless Machine Framework
A modern TypeScript testing framework with a fluent API for building complex test scenarios with context management and assertions. Designed for comprehensive integration testing with support for HTTP requests, database operations, and custom workflows.
Features
- 🔄 Fluent API: Chain steps and assertions naturally with method chaining
- 📋 Context Schema: Type-safe shared state with schema builder and prefix utilities
- 🌐 HTTP Testing: Built-in HTTP request steps and assertions for API testing
- 🗄️ Database Integration: Custom repository steps for database operations
- 🔧 Extensible: Create custom steps, assertions, and value wrappers
- ⚡ Async Support: Full promise-based execution with parallel step support
- 🎯 Type Safe: Complete TypeScript support with generic context typing and schema validation
- 🚀 Bun Native: Optimized for Bun runtime with fast execution
- 📊 Value Wrappers: Flexible value abstraction (static, context refs, computed)
- 🔄 Copy Strategies: Flexible data copying between steps with AllCopy and CompositeCopy
- 🔍 Value Extraction: VExtractor for extracting values from wrappers
Quick Start
Installation
# Install from JSR bun add @alator21/jobless-machine
TypeScript Configuration
For full type safety, ensure your tsconfig.json includes these settings:
{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true } }
Why these settings matter:
strict: true- Enables all strict type checking (usually already enabled)noUncheckedIndexedAccess: true- Critical for this framework - ensures proper type narrowing for indexed access
Without noUncheckedIndexedAccess, TypeScript won't catch certain type mismatches when using the schema validation features. See tsconfig.recommended.json in the package for complete recommended settings.
Basic Usage
import { createTypedSchema, type Prefixed, } from "./src/context/ContextSchema.ts"; import { PrintStep } from "./src/steps/common/PrintStep.ts"; // Define field types for reuse type UserFields = { id: string; name: string; }; // Create schema with prefixed context types const schema = createTypedSchema<Prefixed<"user.", UserFields>>(); // Create and execute runner await schema .createRunner() .addStep(PrintStep.create("Starting test...")) .addStep(CreateUserStep.create(schema.allCopy("user."))) .addStep(PrintStep.create("Test completed")) .execute();
Context Inspection and Debugging
Monitor context state after each step for debugging:
import { createTypedSchema, type Prefixed, } from "./src/context/ContextSchema.ts"; import { PrintStep } from "./src/steps/common/PrintStep.ts"; type UserFields = { id: string; name: string; }; const schema = createTypedSchema<Prefixed<"user.", UserFields>>(); // Built-in debug inspector await schema .createRunner() .withDebugInspector() .addStep(PrintStep.create("Creating user...")) .addStep(CreateUserStep.create(schema.allCopy("user."))) .assertThat(UserExistsAssertion.create("user.")) .execute(); // Custom inspector with your logic const customInspector = (context, stepOrAssertion, stepIndex) => { console.log(`Step ${stepIndex + 1} completed: ${stepOrAssertion.type()}`); const userIds = context.get("user.id"); console.log(`Current user IDs in context: ${userIds.length}`); }; await schema .createRunner() .withInspector(customInspector) .addStep(/* your steps */) .execute();
HTTP API Testing Example
import { createTypedSchema, type Prefixed, } from "./src/context/ContextSchema.ts"; import { SimpleValueWrapper } from "./src/value/SimpleValueWrapper.ts"; import { HttpAssertion } from "./src/assertion/http/HttpAssertion.ts"; type UserFields = { id: string; name: string; email: string; }; const userPrefix = "user."; const schema = createTypedSchema<Prefixed<typeof userPrefix, UserFields>>(); await schema .createRunner() .addStep( CreateUserHttpStep.create(schema.allCopy(userPrefix)) .withName(SimpleValueWrapper.value("John Doe")) .withEmail(SimpleValueWrapper.value("john@example.com")), ) .assertThat(HttpAssertion.success(userPrefix)) .addStep( UpdateUserHttpStep.create( schema.allCopy(userPrefix), schema.value(`${userPrefix}id`), ).withName(SimpleValueWrapper.value("John Smith")), ) .assertThat(HttpAssertion.success(userPrefix)) .execute();
Core Concepts
Context Schema
The recommended way to create typed contexts with schema utilities:
createTypedSchema<T>()- Creates a schema with type-safe helpersschema.createRunner()- Creates a runner with built-in extractorschema.value(key, index?)- Creates type-safe context value wrapperschema.allCopy(prefix)- Creates type-validated copy strategyschema.assertValue(key, callback)- Creates type-safe assertionschema.assertEqual(key1, key2)- Asserts two values are equalschema.assertNotEqual(key1, key2)- Asserts two values are not equalschema.context- Access underlying contextschema.extractor- Access VExtractor instance
Prefixed Type Utility
Helper type for creating prefixed field types:
type Prefixed<P extends string, T extends Record<string, unknown>>
Transforms { id: string } → { "prefix.id": string }
Runner
The main orchestrator that manages test execution flow:
Runner.createWithContext<T>(context, extractor)- Creates runner with context and extractoraddStep(step)- Adds a step to the execution timelineaddStepTimes(step, times)- Adds the same step multiple timesassertThat(assertion)- Adds an assertion to the timelinewithInspector(inspector)- Adds custom context inspection after each step/assertionwithDebugInspector()- Adds built-in debug logging of context stateexecute()- Runs all steps and assertions sequentially
Context
Type-safe key-value store for sharing data between steps:
add<K>(key, value)- Store a value wrapperget<K>(key)- Retrieve all values for a keygetSpecific<K>(key, index)- Get value at specific indexgetRandom<K>(key)- Get a random valuegetRandomAndRemove<K>(key)- Get and remove a random value
Value Wrapper System
Flexible value abstraction for deferred resolution:
SimpleValueWrapper- Wraps static valuesContextValueWrapper- References values stored in contextProviderValueWrapper- Lazily computed valuesDummyValueWrapper- Placeholder values
VExtractor
Extracts actual values from value wrappers:
extract<Val>(wrapper)- Extracts value from any wrapper typeextractSpecificFromContext<Val, K>(key)- Extracts specific context value- Handles SimpleValue, ContextValue, and ProviderValue wrappers
Steps
Actions that can be executed in sequence:
PrintStep- Logs messages to consoleWaitStep- Waits for specified durationHttpRequestStep- Makes HTTP requestsCompositeParallelStep- Executes multiple steps in parallel- Custom steps by extending
Step<T>class
Assertions
Validations that run against context and external systems:
ContextAssertion- Validates context valuesHttpAssertion- Validates HTTP response status and data- Custom assertions by extending
Assertion<T>class
Copy Strategies
Control how data flows between steps:
AllCopy- Copies all step outputs to context with prefixCompositeCopy- Combines multiple copy strategiesSimpleCopy- Basic copying functionality
Project Structure
src/ ├── runner/ # Main test execution engine ├── context/ # Type-safe context management │ ├── Context.ts # Core context implementation │ └── ContextSchema.ts # Schema builder with utilities ├── steps/ # Built-in and custom step implementations │ ├── common/ # Common steps (Print, Wait) │ └── http/ # HTTP request steps ├── assertion/ # Assertion framework │ └── http/ # HTTP-specific assertions ├── value/ # Value wrapper system │ ├── ValueWrapper.ts # Base interface │ ├── SimpleValueWrapper.ts # Static values │ ├── ContextValueWrapper.ts # Context references │ └── ProviderValueWrapper.ts # Computed values ├── copy/ # Data copying strategies └── extractors/ # Value extraction utilities └── VExtractor.ts # Extract values from wrappers
Examples
The framework includes a complete Pet Shop API example demonstrating:
- RESTful API testing with HTTP steps
- Database repository integration
- Complex entity validation
- Multi-step workflows with context sharing
Run the example:
bun run dev # Starts the pet shop API server bun test examples/pet-shop-api/tests/owner.test.ts
Development
Running Tests
bun test # Run all tests bun test --watch # Run tests in watch mode
Type Checking
bun run typecheck # Type check without compilation
Building
bun run build # Build for distribution
Formatting
bun run format # Format code with Prettier
Creating Custom Steps
import { Step } from "./src/steps/Step"; import { Copy } from "./src/copy/Copy"; class MyCustomStep<T extends Record<string, unknown>> extends Step<T> { constructor(copy: Copy<T>) { super(copy); } static create<T extends Record<string, unknown>>( copy: Copy<T>, ): MyCustomStep<T> { return new MyCustomStep(copy); } type(): string { return "MyCustomStep"; } async run(): Promise<void> { await super.run(); // Your custom logic here console.log("Executing custom step logic"); } protected ignoredProperties(): string[] { return []; // Properties to exclude from context copying } }
Creating Custom Assertions
import { Assertion } from "./src/assertion/Assertion"; import { VExtractor } from "./src/extractors/VExtractor"; class MyCustomAssertion< T extends Record<string, unknown>, > extends Assertion<T> { constructor() { super(); } static create<T extends Record<string, unknown>>(): MyCustomAssertion<T> { return new MyCustomAssertion<T>(); } type(): string { return "MyCustomAssertion"; } async validate(extractor: VExtractor<T>): Promise<void> { await super.validate(extractor); // Your validation logic here const value = extractor.extractSpecificFromContext("someKey"); if (!value) { throw new Error("Expected value not found in context"); } } }
Publishing
This package is published to JSR (JavaScript Registry).
For TypeScript configuration recommendations, see the "TypeScript Configuration" section above or check tsconfig.recommended.json included in the package.
License
ISC
Add Package
deno add jsr:@alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";
Import directly with a jsr specifier
import * as jobless_machine from "jsr:@alator21/jobless-machine";
Add Package
pnpm i jsr:@alator21/jobless-machine
pnpm dlx jsr add @alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";
Add Package
yarn add jsr:@alator21/jobless-machine
yarn dlx jsr add @alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";
Add Package
vlt install jsr:@alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";
Add Package
npx jsr add @alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";
Add Package
bunx jsr add @alator21/jobless-machine
Import symbol
import * as jobless_machine from "@alator21/jobless-machine";