Skip to content

Testing

dreamcli's test harness runs commands in-process with full control over inputs and outputs. No subprocesses, no process.argv mutation, no mocking.

Basic Usage

ts
import { runCommand } from 'dreamcli/testkit';

const result = await runCommand(greet, ['Alice', '--loud']);

expect(result.exitCode).toBe(0);
expect(result.stdout).toEqual(['HELLO, ALICE!\n']);
expect(result.stderr).toEqual([]);
expect(result.error).toBeUndefined();

RunOptions

Control every dimension of CLI behavior from tests:

ts
const result = await runCommand(deploy, ['production'], {
	env: { DEPLOY_REGION: 'eu' }, // environment variables
	config: { deploy: { region: 'us' } }, // config file values
	answers: ['ap'], // prompt answers (consumed in order)
	json: true, // simulate --json mode
	verbosity: 'quiet', // verbosity level
});

Available Options

OptionTypeDescription
envRecord<string, string>Environment variables
configRecord<string, unknown>Config file values
answersunknown[]Prompt answers in order
prompterPromptEngineCustom prompt handler
jsonbooleanSimulate --json mode
helpbooleanSimulate --help
verbositystringOutput verbosity level
adapterRuntimeAdapterCustom runtime adapter

Testing Prompts

ts
import { runCommand, createTestPrompter, PROMPT_CANCEL } from 'dreamcli/testkit';

// Sequential answers
const result = await runCommand(cmd, [], {
	answers: ['eu', true, 'my-name'],
});

// Simulate prompt cancellation
const result = await runCommand(cmd, [], {
	prompter: createTestPrompter([PROMPT_CANCEL]),
});

Asserting Activity Events

Spinners and progress bars emit testable events:

ts
const result = await runCommand(cmd, ['deploy']);

expect(result.activity).toContainEqual(expect.objectContaining({ type: 'spinner:start' }));
expect(result.activity).toContainEqual(expect.objectContaining({ type: 'spinner:stop' }));

Captured Output

ts
// stdout lines (each includes trailing \n)
result.stdout; // string[]

// stderr lines
result.stderr; // string[]

// Exit code
result.exitCode; // number

// Error (if action threw)
result.error; // Error | undefined

// Activity events (spinner/progress)
result.activity; // ActivityEvent[]

Design Philosophy

  • No lifecycle hooks — isolation comes from the testkit architecture
  • No snapshots — all assertions are explicit
  • No mocking — use RunOptions injection instead of vi.mock()
  • No process.argv — everything is passed as parameters

What's Next?

  • Commands — building commands to test
  • Output — output channel behavior

Released under the MIT License.