Oclif prompt testing

2k Views Asked by At

I'm attempting to write a unit test for an Oclif hook that contains a simple prompt. I want to test the output of the hook, given a 'Y' or 'N' response to the prompt.

import {Hook} from '@oclif/config'
import cli from 'cli-ux'

const hook: Hook<'init'> = async function () {

  const answer = await cli.prompt("Y or N?")

  if(answer === 'Y') {
    this.log('yes')
  }
  else {
    this.log('no')
  }
}

export default hook

I'm using the 'fancy-test' and '@oclif/test' test frameworks described here: https://oclif.io/docs/testing

I have tried stubbing the prompt and simulating stdin but neither are working - either the stubbed function is not available or the output is an empty string.

Here's an attempt at one test (doesn't work because 'cli.prompt is not a function'):

import {expect, test} from '@oclif/test'
import cli from 'cli-ux'
import * as sinon from 'sinon';

describe('it should test the "configure telemetry" hook', () => {
  test
  .stub(cli, 'prompt', sinon.stub().resolves('Y'))
  .stdout()
  .hook('init')
  .do(output => expect(output.stdout).to.contain('yes'))
  .it()
})

It occurred to me that I'm probably not structuring my test properly. If anyone could point me in the right direction or provide some pseudo / sample code as to how to approach testing the above hook that would be amazing - thanks!

2

There are 2 best solutions below

4
On BEST ANSWER

Have you tried with:

import {expect, test} from '@oclif/test'
import cli from 'cli-ux'
import * as sinon from 'sinon';

describe('it should test the "configure telemetry" hook', () => {
  test
  .stub(cli, 'prompt', () => async () => 'Y')
  .stdout()
  .hook('init')
  .do(output => expect(output.stdout).to.contain('yes'))
  .it()
})

Stubbing with .stub(cli, 'prompt', () => async () => 'Y') worked for me

0
On

I have found a way to test multiple calls to ux.prompt, no extra libraries needed aside from the default @oclif/core (which is actually an extension of fancy-test):

Let's suppose we have a single-command-cli Hello, then our src/index.ts file would look like this:

import {Command, Flags, ux} from '@oclif/core'

export default class Hello extends Command {
  static description = 'describe the command here'

  static examples = [
    `$ myplugin hello
hello world from ./src/hello.ts!
`,
  ]

  static flags = {
    help: Flags.help({char: 'h'}),
    // add --version flag to show CLI version
    version: Flags.version({char: 'v'}),
  }

  interactive = async () => {
    const object = {
      email: '',
      name: '',
      phone: '',
    }

    const name = await ux.prompt('What is your name?')
    const email = await ux.prompt('What is your email?')
    const phone = await ux.prompt('What is your phone number?')

    object.name = name
    object.email = email
    object.phone = phone

    return object
  }

  async run(): Promise<void> {
    const object = await this.interactive()

    console.log(`Hello ${object.name}!`)
    console.log(`Your email is ${object.email}`)
    console.log(`Your phone number is ${object.phone}`)
  }
}

Our test/index.test.tes file will look like this:


import {ux} from '@oclif/core'
import {expect, test} from '@oclif/test'

import Hello from '../src/index'

const promptStub = (stub) => {
  stub.onFirstCall().resolves('John Doe') // Alias for stub.onCall(0).resolves('John Doe')
  stub.onSecondCall().resolves('[email protected]') // Alias for stub.onCall(1).resolves('[email protected]') 
  stub.onThirdCall().resolves('123-456-7890') // Alias for stub.onCall(2).resolves('123-456-7890')
  return stub
}

describe('basic usage', () => {
  test
    .stdout({print: true})
    .stub(ux, 'prompt', promptStub)
    .do(() => Hello.run([]))
    .it('runs hello', (ctx) => {
      expect(ctx.stdout).to.contain('Hello John Doe!')
      expect(ctx.stdout).to.contain('Your email is [email protected]')
      expect(ctx.stdout).to.contain('Your phone number is 123-456-7890')
    })
})


We need understand that stub is part of the Sinon.JS library, and that we can use some of the stub features in our oclif tests. Data types are specially important if we are using TypeScript.

In addition, to test multiple-command-cli's just replace .do(() => Hello.run([])) with the regular .command() calls.