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 '@kjanat/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
import { runCommand } from '@kjanat/dreamcli/testkit';

await runCommand(deploy, ['production'], {
  // environment variables
  env: { DEPLOY_REGION: 'eu' },
});

await runCommand(deploy, ['production'], {
  // config file values
  config: { deploy: { region: 'us' } },
});

await runCommand(deploy, ['production'], {
  // prompt answers (consumed in order)
  answers: ['ap'],
});

await runCommand(deploy, ['production'], {
  // piped stdin for args configured with .stdin()
  stdinData: '<your input>',
  // simulate --json mode
  jsonMode: true,
  // verbosity level
  verbosity: 'quiet',
  // simulate TTY output
  isTTY: true,
});

Available Options

OptionTypeDescription
envRecord<string, string | undefined>Environment variables
configRecord<string, unknown>Config file values
stdinDatastring | nullData supplied to command stdin for .stdin() args
answersunknown[]Prompt answers in order
prompterPromptEngineCustom prompt handler
jsonModebooleanSimulate --json mode
helpHelpOptionsHelp formatting options
verbosityVerbosityOutput verbosity level
isTTYbooleanSimulate a TTY stdout connection

Testing Prompts

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

// Sequential answers
await runCommand(promptCmd, [], {
  answers: ['eu'],
});

// Simulate prompt cancellation
await runCommand(promptCmd, [], {
  prompter: createTestPrompter([PROMPT_CANCEL]),
});

Asserting Activity Events

Spinners and progress bars emit testable events:

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

const result = await runCommand(activityCmd, []);

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

Captured Output

ts
import type { RunResult } from '@kjanat/dreamcli/testkit';

type CapturedOutput = Pick<
  RunResult,
  'stdout' | 'stderr' | 'exitCode' | 'error'
>;

declare const captured: CapturedOutput;

activity is tracked separately from captured output and is covered in the section above.

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?

Released under the MIT License.