Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

@inquirer/testing

The @inquirer/testing package is Inquirer's answer to testing prompts built with @inquirer/core.

Installation

npm yarn
npm install @inquirer/testing --save-dev
yarn add @inquirer/testing --dev

Usage

This package provides two ways to test Inquirer prompts:

  1. Unit testing with render() - Test individual prompts in isolation
  2. E2E testing with screen - Test full CLI applications that use Inquirer

Unit Testing with render()

The render() function creates and instruments a command line interface for testing a single prompt.

import { render } from '@inquirer/testing';
import input from '@inquirer/input';

describe('input prompt', () => {
  it('handle simple use case', async () => {
    const { answer, events, getScreen } = await render(input, {
      message: 'What is your name',
    });

    expect(getScreen()).toMatchInlineSnapshot(`"? What is your name"`);

    events.type('J');
    expect(getScreen()).toMatchInlineSnapshot(`"? What is your name J"`);

    events.type('ohn');
    events.keypress('enter');

    await expect(answer).resolves.toEqual('John');
    expect(getScreen()).toMatchInlineSnapshot(`"? What is your name John"`);
  });
});

render() API

render takes 2 arguments:

  1. The Inquirer prompt to test (the return value of createPrompt())
  2. The prompt configuration (the first prompt argument)

render returns a promise that resolves once the prompt is rendered. This promise returns:

  • answer (Promise) - Resolves when an answer is provided and valid
  • getScreen (({ raw?: boolean }) => string) - Returns the current screen content. By default strips ANSI codes
  • nextRender (() => Promise<void>) - Wait for the next screen update. Use after triggering async actions (e.g. pressing enter with validation). Coalesces rapid back-to-back renders so a single await nextRender() captures the final settled state
  • events - Utilities to interact with the prompt:
    • keypress(key: string | KeyObject) - Trigger a keypress event
    • type(text: string) - Type text into the prompt
  • getFullOutput (() => Promise<string>) - Returns the full output interpreted through a virtual terminal, resolving ANSI escape sequences into the actual screen state

Async actions and nextRender()

When a keypress triggers an asynchronous action (such as input validation), the screen won't update synchronously. Use nextRender() to wait for the prompt to settle before reading the screen:

import { render } from '@inquirer/testing';
import input from '@inquirer/input';

it('shows a validation error', async () => {
  const { answer, events, getScreen, nextRender } = await render(input, {
    message: 'Enter a number',
    validate: (value) => /^\d+$/.test(value) || 'Must be a number',
  });

  events.type('abc');
  events.keypress('enter');

  await nextRender(); // wait for validation to complete and the error to render
  expect(getScreen()).toContain('Must be a number');

  events.keypress('backspace');
  events.keypress('backspace');
  events.keypress('backspace');
  events.type('42');
  events.keypress('enter');

  await expect(answer).resolves.toEqual('42');
});

Unit Testing Example

You can refer to the @inquirer/input test suite for a comprehensive unit testing example using render().

E2E Testing with screen

For testing full CLI applications that use Inquirer prompts internally, use the framework-specific entry points:

Vitest

import { describe, it, expect } from 'vitest';
import { screen } from '@inquirer/testing/vitest';

// Import your CLI AFTER @inquirer/testing/vitest
import { runMyCli } from './my-cli.js';

describe('my CLI', () => {
  it('asks for name and confirms', async () => {
    const result = runMyCli();

    // First prompt is immediately available
    expect(screen.getScreen()).toContain('What is your name?');
    screen.type('John');
    screen.keypress('enter');

    // Wait for next prompt
    await screen.next();
    expect(screen.getScreen()).toContain('Confirm?');
    screen.keypress('enter');

    await result;
  });
});

Jest

import { screen } from '@inquirer/testing/jest';
import { runMyCli } from './my-cli.js';

describe('my CLI', () => {
  it('asks for name and confirms', async () => {
    const result = runMyCli();

    // First prompt is immediately available
    expect(screen.getScreen()).toContain('What is your name?');
    screen.type('John');
    screen.keypress('enter');

    // Wait for next prompt
    await screen.next();
    expect(screen.getScreen()).toContain('Confirm?');
    screen.keypress('enter');

    await result;
  });
});

screen API

The screen object provides:

  • next() - Wait for the next screen update (prompt transitions, validation errors, async updates). The initial prompt render is available immediately via getScreen() — no next() needed
  • getScreen({ raw?: boolean }) - Get the current prompt screen content. By default strips ANSI codes
  • getFullOutput({ raw?: boolean }) - Get all accumulated output interpreted through a virtual terminal (returns a Promise). By default resolves ANSI escape sequences into actual screen state
  • type(text) - Type text (writes to stream AND emits keypresses)
  • keypress(key) - Send a keypress event
  • clear() - Reset screen state (called automatically before each test)

Mocking Third-Party Prompts

All @inquirer/* prompts are mocked automatically. To mock a third-party or custom prompt package, use wrapPrompt in your own mock call:

Vitest

import { screen, wrapPrompt } from '@inquirer/testing/vitest';

vi.mock('@my-company/custom-prompt', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@my-company/custom-prompt')>();
  return { ...actual, default: wrapPrompt(actual.default) };
});

Jest

In Jest, jest.mock() factories are hoisted before imports, so wrapPrompt must be accessed via jest.requireActual() inside the factory:

import { screen } from '@inquirer/testing/jest';

jest.mock('@my-company/custom-prompt', () => {
  const { wrapPrompt } = jest.requireActual('@inquirer/testing/jest');
  const actual = jest.requireActual('@my-company/custom-prompt');
  return { ...actual, default: wrapPrompt(actual.default) };
});

Important Notes

  1. Import order matters: Import @inquirer/testing/vitest or @inquirer/testing/jest BEFORE importing modules that use Inquirer prompts
  2. Editor prompt: The external editor is mocked — screen.type() buffers text, and screen.keypress('enter') submits it (same pattern as other prompts). Works with both waitForUserInput: true and false
  3. Sequential prompts: Multiple prompts are supported, but they must run sequentially (not concurrently)

E2E Testing Example

You can refer to the @inquirer/demo test suite for a comprehensive E2E testing example using screen.

License

Copyright (c) 2023 Simon Boudrias (twitter: @vaxilart)
Licensed under the MIT license.