From ad4e9aa5c0ed6537de435f62f4814bba2ea0fc0f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 22 May 2024 13:35:23 -0600 Subject: [PATCH] fix: update testing docs --- docs/testing.md | 66 +++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index b09837ef..1cb1380e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -5,23 +5,22 @@ description: How to test your CLI Testing in oclif can be done with any testing framework. You can run commands with `MyCommand.run()` which returns a promise you can wait on. -There are common tasks however when writing CLI tools. For this, we have a conventional set of tools that we suggest using to test your CLI. These are based on [mocha](https://mochajs.org) and [fancy-test](https://github.com/jdxcode/fancy-test). +There are common tasks however when writing CLI tools. For this, we have [@oclif/test](https://github.com/oclif/test), which provides a [conventional set of utilities](https://github.com/oclif/test?tab=readme-ov-file#usage) that we find useful for testing oclif CLIs. -Mocha is the top JavaScript testing framework and a solid choice for any project. fancy-test is a tool we developed that builds on top of mocha to make it easy to repeat patterns and write concise mocha tests. There is also a library [@oclif/test](https://github.com/oclif/test) that extends fancy-test with helpers specific to testing oclif CLIs. These are things like running a command or hook or checking if an exit status code is set, for example. +Any CLI built with oclif will come preloaded with [mocha](https://www.npmjs.com/package/mocha) (our preferred testing framework but you're free to use whatever you prefer) and [@oclif/test](https://github.com/oclif/test) as well as an example test that should work out of the box with `npm test` or `yarn test`. -Any CLI built with oclif will come preloaded with these tools and an example test that should work out of the box with `npm test` or `yarn test`. -As an example, let's look at the `heroku whoami` command which makes an API call to get the current logged in user. If the user is not logged in, it exits with status 100. (This is a simplified example, here is [the actual code](https://github.com/heroku/heroku-cli-plugin-auth).) +As an example, let's look at this `whoami` command which makes an API call to get the current logged in user. If the user is not logged in, it exits with status 100. **src/commands/whoami.ts** ```js -import Command from '@heroku-cli/command' +import {Command} from '@oclif/core' export class Whoami extends Command { async run() { try { - let {body: account} = await this.heroku.get('/account') + let {body: account} = await this.api.get('/account') this.log(account.email) } catch (err) { if (err.statusCode === 401) { @@ -40,43 +39,34 @@ Here is the test code **test/commands/whoami.test.ts** ```typescript -import {expect, test} from '@oclif/test' - -describe('auth:whoami', () => { - test - .nock('https://api.heroku.com', api => api - .get('/account') - // user is logged in, return their name - .reply(200, {email: 'jeff@example.com'}) - ) - .stdout() - .command(['auth:whoami']) - .it('shows user email when logged in', ctx => { - expect(ctx.stdout).to.equal('jeff@example.com\n') +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('whoami', () => { + it('shows user email when logged in', async () => { + nock('https://api.example.com') + .get('/account') + // user is logged in, return their name + .reply(200, {email: 'jeff@example.com'}) + + const {stdout} = await runCommand('whoami') + expect(stdout).to.equal('jeff@example.com') }) - test - .nock('https://api.heroku.com', api => api - .get('/account') - // HTTP 401 means the user is not logged in with valid credentials - .reply(401) - ) - .command(['auth:whoami']) - // checks to ensure the command exits with status 100 - .exit(100) - .it('exits with status 100 when not logged in') + it('exits with status 100 when not logged in', async () => { + nock('https://api.example.com') + .get('/account') + // HTTP 401 means the user is not logged in with valid credentials + .reply(401) + + const {error} = await runCommand('whoami') + expect(error?.oclif?.exit).to.equal(100) + }) }) ``` -These tools are setup to not only mock out the stdout/stderr and HTTP calls, but they're setup to ensure they automatically reset after the test. A common issue we've had when building CLIs with simpler `beforeEach/afterEach` filters is that if the `afterEach` filters aren't setup correctly, a failing test can leave mocks around that make later tests fail. Using fancy-test, we avoid this problem and only have to declare our mocks once. - -For more on how to test with oclif, check out the docs for [fancy-test](https://github.com/jdxcode/fancy-test) and [@oclif/test](https://github.com/oclif/test). - -## stdout/stderr - -The stdout/stderr mocks use [stdout-stderr](https://github.com/jdxcode/stdout-stderr) under the hood. This library can be used standalone if you'd prefer to use jest or want a different testing setup but still have the ability to mock out stdout and stderr. - -If you want to see the output but leave it mocked, you can either pass in `{print: true}` to the options, or set `TEST_OUTPUT=1`. +For more on how to test with oclif, check out the docs for [@oclif/test](https://github.com/oclif/test). ## Code Coverage