Skip to content

Commit

Permalink
fix: update testing docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed May 22, 2024
1 parent bef1c9f commit ad4e9aa
Showing 1 changed file with 28 additions and 38 deletions.
66 changes: 28 additions & 38 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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: '[email protected]'})
)
.stdout()
.command(['auth:whoami'])
.it('shows user email when logged in', ctx => {
expect(ctx.stdout).to.equal('[email protected]\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: '[email protected]'})

const {stdout} = await runCommand('whoami')
expect(stdout).to.equal('[email protected]')
})

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

Expand Down

0 comments on commit ad4e9aa

Please sign in to comment.