Cliffy

Snapshot testing

The snapshotTest method can be used to test stdin, stdout and stderr of a single test case. It injects data to stdin and snapshots the stdout and stderr output of each test case separately.

Usage

The snapshotTest method behaves like a combination of Deno.test() and the assertSnapshot method from the deno std library. The name, meta and fn options are required.

Basic usage

This example snapshots the output of console.log and console.error.

import { snapshotTest } from "@cliffy/testing";

await snapshotTest({
  name: "should log to stdout and stderr",
  meta: import.meta,
  async fn() {
    console.log("foo");
    console.error("bar");
  },
});Copy

To update the snapshots, run deno test -- --update. This creates a snapshot file at __snapshots__/[filename].snap with the following content:

export const snapshot = {};

snapshot[`should log to stdout and stderr 1`] = `
"stdout:
foo

stderr:
bar
"
`;Copy

If you now run deno test (without -- --update), it will check if your test function still has the same output as the snapshot file content has.

Script arguments

Arguments defined with the args option are injected into the test method as script args. You can simply use Deno.args as you normally would to get the script arguments.

import { snapshotTest } from "@cliffy/testing";

await snapshotTest({
  name: "should log Deno.args",
  meta: import.meta,
  args: ["--foo", "bar"],
  async fn() {
    console.log(Deno.args);
  },
});Copy

You can use this to create snapshot tests for commands.

import { snapshotTest } from "@cliffy/testing";
import { Command } from "@cliffy/command";

await snapshotTest({
  name: "should execute the command with the --foo option",
  meta: import.meta,
  args: ["--foo", "bar"],
  async fn() {
    await new Command()
      .name("example")
      .description("Example command.")
      .option("-f, --foo <bar:string>", "Example option.")
      .action(({ foo }) => {
        console.log("foo: %s", foo);
      })
      .parse();
  },
});Copy

Stdin

The snapshotTest method can inject data to the test function with the stdin option. You can simply read the data from Deno.stdin as you normally would when reading data from stdin.

import { snapshotTest } from "@cliffy/testing";

await snapshotTest({
  name: "should read cliffy from stdin",
  meta: import.meta,
  stdin: ["cliffy"],
  async fn() {
    let name = "";
    const decoder = new TextDecoder();
    for await (const chunk of Deno.stdin.readable) {
      name += decoder.decode(chunk);
      if (name === "cliffy") {
        break;
      }
    }
    console.log("name:", name);
  },
});Copy

You can use this to create snapshot tests for prompts. The ansi module can be used to generate escape sequences to control the prompt.

import { snapshotTest } from "@cliffy/testing";
import { Select } from "@cliffy/prompt";
import { ansi } from "@cliffy/ansi";

await snapshotTest({
  name: "should select a color",
  meta: import.meta,
  stdin: ansi
    .cursorDown
    .cursorDown
    .text("\n")
    .toArray(),
  async fn() {
    const name = await Select.prompt({
      message: "Select a color",
      options: ["red", "green", "blue"],
    });
    console.log("name:", name);
  },
});Copy

Test steps

You can also add multiple steps to the test function. The snapshotTest method then calls the test function once for each step within a separate test step by calling t.step() from the test context. Each step can have separate options for stdin and args.

import { snapshotTest } from "@cliffy/testing";

await snapshotTest({
  name: "should log to stdout and atderr",
  meta: import.meta,
  steps: {
    "step 1": { args: ["foo"], stdin: ["bar"] },
    "step 2": { args: ["beep"], stdin: ["boop"] },
  },
  async fn() {
    console.log(Deno.args);
  },
});Copy