{"id":12977,"library":"coffee","title":"Coffee CLI Testing","description":"Coffee is a Node.js library designed for robust and fluent testing of command-line interfaces (CLIs). It abstracts the complexities of `child_process.fork` and `child_process.spawn`, providing a streamlined API for executing and asserting against CLI outputs and exit codes. The library, currently stable at version 5.5.1, offers powerful assertion chains for standard output (stdout), standard error (stderr), and process exit codes, supporting both exact string matches and regular expressions. Coffee differentiates itself with features like a debug mode for printing live stdio, interaction capabilities for prompts, and an extensible architecture for creating custom assertion rules. It ships with TypeScript type definitions, making it well-suited for modern JavaScript and TypeScript development workflows, often integrated into test frameworks like Mocha or Jest.","status":"active","version":"5.5.1","language":"javascript","source_language":"en","source_url":"git://github.com/node-modules/coffee","tags":["javascript","cli","test","shell","spawn","fork","child_process","exec","typescript"],"install":[{"cmd":"npm install coffee","lang":"bash","label":"npm"},{"cmd":"yarn add coffee","lang":"bash","label":"yarn"},{"cmd":"pnpm add coffee","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"This is the default export, providing the main `coffee` instance with `fork` and `spawn` methods. For modern Node.js and TypeScript, ESM `import` is preferred over CommonJS `require()`.","wrong":"const coffee = require('coffee');","symbol":"coffee","correct":"import coffee from 'coffee';"},{"note":"The `Coffee` class is a named export, used when creating custom `coffee` instances (e.g., `class MyCoffee extends Coffee { ... }`) to extend its core functionality or modify behavior.","wrong":"const { Coffee } = require('coffee');","symbol":"Coffee","correct":"import { Coffee } from 'coffee';"},{"note":"The `Rule` class is a named export, primarily used as a base for developing custom assertion rules (e.g., `class FileRule extends Rule { ... }`) to introduce new `expect` types.","wrong":"const { Rule } = require('coffee');","symbol":"Rule","correct":"import { Rule } from 'coffee';"}],"quickstart":{"code":"import coffee from 'coffee';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\n// Helper to get __dirname in ESM context\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst tempDir = path.join(__dirname, '.coffee-temp');\nconst cliScriptPath = path.join(tempDir, 'my-cli-test.js');\n\n// Create a dummy CLI script for demonstration purposes.\n// In a real project, this path would point to your actual CLI entry file.\nfs.mkdirSync(tempDir, { recursive: true });\nfs.writeFileSync(cliScriptPath, `\n// my-cli-test.js\nconsole.log('Hello, ' + process.argv[2] + '!');\nconsole.error('Debug: CLI ran.');\nprocess.exit(parseInt(process.argv[3] || '0', 10));\n`);\n\n// Example of how you might use coffee in a test file (e.g., test/my.test.ts)\ndescribe('My CLI application', () => {\n  // Clean up the temporary file after all tests\n  afterAll(() => {\n    fs.unlinkSync(cliScriptPath);\n    fs.rmdirSync(tempDir);\n  });\n\n  it('should execute with arguments and assert stdout/stderr/code', async () => {\n    // `coffee.fork` is used for Node.js scripts\n    await coffee.fork(cliScriptPath, ['World', '0'])\n      .expect('stdout', 'Hello, World!\\n')\n      .expect('stderr', 'Debug: CLI ran.\\n')\n      .expect('code', 0) // Expect a successful exit code\n      .end(); // Important to call .end() to finalize the assertion chain\n  });\n\n  it('should handle different exit codes', async () => {\n    await coffee.fork(cliScriptPath, ['ErrorUser', '1'])\n      .expect('stdout', 'Hello, ErrorUser!\\n')\n      .expect('code', 1) // Expect a non-zero exit code indicating an error\n      .end();\n  });\n\n  it('should spawn a generic shell command', async () => {\n    // `coffee.spawn` is used for non-Node.js shell commands\n    await coffee.spawn('echo', ['Testing spawn'])\n      .expect('stdout', 'Testing spawn\\n')\n      .expect('code', 0)\n      .end();\n  });\n});","lang":"typescript","description":"Demonstrates how to use `coffee.fork` to test a Node.js CLI script with arguments, asserting its stdout, stderr, and exit code, and `coffee.spawn` for general shell commands."},"warnings":[{"fix":"Ensure your project is configured for ESM (e.g., `\"type\": \"module\"` in `package.json` or using `.mjs` files) and update `require('coffee')` statements to `import coffee from 'coffee'`. For custom extensions, use `import { Coffee, Rule } from 'coffee'`.","message":"Transitioning from CommonJS `require()` to ESM `import` for `coffee` in modern Node.js and TypeScript projects might require configuration adjustments (e.g., `type: \"module\"` in `package.json`). Although `coffee` provides CJS compatibility, using `import coffee from 'coffee'` is the idiomatic approach for new code.","severity":"breaking","affected_versions":">=5.0.0"},{"fix":"Always include `\\n` at the end of expected `stdout` or `stderr` strings if the child process typically outputs a newline. Alternatively, use regular expressions (e.g., `/your output\\n?$/`) for more flexible matching.","message":"Assertions using `expect('stdout', '...')` or `expect('stderr', '...')` often require exact string matches, including trailing newline characters (`\\n`). Missing a newline at the end of the expected string is a very common cause of failing tests.","severity":"gotcha","affected_versions":">=0.0.0"},{"fix":"Ensure every `coffee` chain (after `fork` or `spawn` and any `expect` calls) explicitly ends with `.end()` and `await` it. For example: `await coffee.fork(...).expect(...).end();`","message":"Forgetting to call `.end()` at the end of the `coffee` assertion chain will prevent the test from executing the child process and resolving, leading to hanging tests or incorrect results. The `.end()` call signals the completion of the assertion setup and returns a promise.","severity":"gotcha","affected_versions":">=0.0.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Review the CLI script to understand why it's exiting with an error code. If the non-zero code is expected for an error condition, update `expect('code', 0)` to `expect('code', 1)` (or the appropriate error code).","cause":"The command-line program exited with a non-zero status code, indicating an error, while the test expected success (code 0).","error":"AssertionError: Expected exit code 0 but got 1"},{"fix":"Carefully compare the expected string with the actual output. Ensure newline characters (`\\n`) are correctly included or excluded. Consider using regular expressions for more robust matching if exact string comparison is too brittle (e.g., `expect('stdout', /^Hello World\\n?$/)`).","cause":"The expected stdout string did not exactly match the actual output, often due to a missing newline character or subtle whitespace differences.","error":"AssertionError: stdout mismatch, expected 'Hello World\\n' but got 'Hello World'"},{"fix":"Ensure `.end()` is called at the end of your `coffee` assertion chain, and that you `await` the resulting promise. Also, verify that the `coffee` object is correctly initialized and not `null` or `undefined`.","cause":"The `.end()` method was not called at the end of the `coffee` assertion chain, preventing the promise from resolving and the test from completing or executing properly, or a method was called on a non-Coffee object.","error":"TypeError: coffee(...).expect is not a function"},{"fix":"Verify that the command (`<command>`) is correctly spelled and installed on the system where tests are run. Ensure its directory is included in the system's PATH environment variable. For Node.js scripts, consider using `coffee.fork()` instead.","cause":"The command specified in `coffee.spawn('command', ...)` could not be found in the system's PATH. This typically means the executable is not installed or not accessible.","error":"Error: spawn <command> ENOENT"}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":null,"cli_name":""}