Oclif prompt testing
Asked Answered
B

2

9

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!

Baywood answered 27/6, 2019 at 19:15 Comment(0)
W
9

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

Weatherman answered 1/7, 2019 at 13:46 Comment(4)
If you have any pointers about, how we can capture this.warn(message: string | Error) data which continues after giving warning. Thanks In advanceDaddy
Just from the code snippet it's hard to tell. I kept having problem trying to log in oclif as well. Maybe you can open a separate question about it?Weatherman
Does it work for multiple prompts?Abacist
You can write a function within async (input) => { return 'Y'} and return different values depending on what is prompted for.Weatherman
D
0

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.

Dylandylana answered 3/2 at 1:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.