1 | # @oclif/test
|
2 |
|
3 | test helpers for oclif CLIs
|
4 |
|
5 | [![Version](https://img.shields.io/npm/v/@oclif/test.svg)](https://npmjs.org/package/@oclif/test)
|
6 | [![Downloads/week](https://img.shields.io/npm/dw/@oclif/test.svg)](https://npmjs.org/package/@oclif/test)
|
7 | [![License](https://img.shields.io/npm/l/@oclif/test.svg)](https://github.com/oclif/test/blob/main/package.json)
|
8 |
|
9 | ## Usage
|
10 |
|
11 | `@oclif/test` is an extension of [fancy-test](https://github.com/oclif/fancy-test). Please see the [fancy-test documentation](https://github.com/oclif/fancy-test#fancy-test) for all the features that are available.
|
12 |
|
13 | The following are the features that `@oclif/test` adds to `fancy-test`.
|
14 |
|
15 | ### `.loadConfig()`
|
16 |
|
17 | `.loadConfig()` creates and returns a new [`Config`](https://github.com/oclif/core/blob/main/src/config/config.ts) instance. This instance will be available on the `ctx` variable that's provided in the callback.
|
18 |
|
19 | ```typescript
|
20 | import {join} from 'node:path'
|
21 | import {expect, test} from '@oclif/test'
|
22 |
|
23 | const root = join(__dirname, 'fixtures/test-cli')
|
24 | test
|
25 | .loadConfig({root})
|
26 | .stdout()
|
27 | .command(['foo:bar'])
|
28 | .it('should run the command from the given directory', (ctx) => {
|
29 | expect(ctx.stdout).to.equal('hello world!\n')
|
30 | expect(ctx.config.root).to.equal(root)
|
31 | const {name} = ctx.returned as {name: string}
|
32 | expect(name).to.equal('world')
|
33 | })
|
34 | ```
|
35 |
|
36 | If you would like to run the same test without using `@oclif/test`:
|
37 |
|
38 | ```typescript
|
39 | import {Config, ux} from '@oclif/core'
|
40 | import {expect} from 'chai'
|
41 | import {join} from 'node:path'
|
42 | import {SinonSandbox, SinonStub, createSandbox} from 'sinon'
|
43 |
|
44 | const root = join(__dirname, 'fixtures/test-cli')
|
45 | describe('non-fancy test', () => {
|
46 | let sandbox: SinonSandbox
|
47 | let config: Config
|
48 | let stdoutStub: SinonStub
|
49 |
|
50 | beforeEach(async () => {
|
51 | sandbox = createSandbox()
|
52 | stdoutStub = sandbox.stub(ux.write, 'stdout')
|
53 | config = await Config.load({root})
|
54 | })
|
55 |
|
56 | afterEach(async () => {
|
57 | sandbox.restore()
|
58 | })
|
59 |
|
60 | it('should run command from the given directory', async () => {
|
61 | const {name} = await config.runCommand<{name: string}>('foo:bar')
|
62 | expect(stdoutStub.calledWith('hello world!\n')).to.be.true
|
63 | expect(config.root).to.equal(root)
|
64 | expect(name).to.equal('world')
|
65 | })
|
66 | })
|
67 | ```
|
68 |
|
69 | ### `.command()`
|
70 |
|
71 | `.command()` let's you run a command from your CLI.
|
72 |
|
73 | ```typescript
|
74 | import {expect, test} from '@oclif/test'
|
75 |
|
76 | describe('hello world', () => {
|
77 | test
|
78 | .stdout()
|
79 | .command(['hello:world'])
|
80 | .it('runs hello world cmd', (ctx) => {
|
81 | expect(ctx.stdout).to.contain('hello world!')
|
82 | })
|
83 | })
|
84 | ```
|
85 |
|
86 | For a [single command cli](https://oclif.io/docs/single_command_cli) you would provide `'.'` as the command. For instance:
|
87 |
|
88 | ```typescript
|
89 | import {expect, test} from '@oclif/test'
|
90 |
|
91 | describe('hello world', () => {
|
92 | test
|
93 | .stdout()
|
94 | .command(['.'])
|
95 | .it('runs hello world cmd', (ctx) => {
|
96 | expect(ctx.stdout).to.contain('hello world!')
|
97 | })
|
98 | })
|
99 | ```
|
100 |
|
101 | If you would like to run the same test without using `@oclif/test`:
|
102 |
|
103 | ```typescript
|
104 | import {Config, ux} from '@oclif/core'
|
105 | import {expect} from 'chai'
|
106 | import {SinonSandbox, SinonStub, createSandbox} from 'sinon'
|
107 |
|
108 | describe('non-fancy test', () => {
|
109 | let sandbox: SinonSandbox
|
110 | let config: Config
|
111 | let stdoutStub: SinonStub
|
112 |
|
113 | beforeEach(async () => {
|
114 | sandbox = createSandbox()
|
115 | stdoutStub = sandbox.stub(ux.write, 'stdout')
|
116 | config = await Config.load({root: process.cwd()})
|
117 | })
|
118 |
|
119 | afterEach(async () => {
|
120 | sandbox.restore()
|
121 | })
|
122 |
|
123 | it('should run command', async () => {
|
124 | // use '.' for a single command CLI
|
125 | const {name} = await config.runCommand<{name: string}>('hello:world')
|
126 | expect(stdoutStub.calledWith('hello world!\n')).to.be.true
|
127 | expect(name).to.equal('world')
|
128 | })
|
129 | })
|
130 | ```
|
131 |
|
132 | ### `.exit()`
|
133 |
|
134 | `.exit()` let's you test that a command exited with a certain exit code.
|
135 |
|
136 | ```typescript
|
137 | import {join} from 'node:path'
|
138 | import {expect, test} from '@oclif/test'
|
139 |
|
140 | describe('exit', () => {
|
141 | test
|
142 | .loadConfig()
|
143 | .stdout()
|
144 | .command(['hello:world', '--code=101'])
|
145 | .exit(101)
|
146 | .do((output) => expect(output.stdout).to.equal('exiting with code 101\n'))
|
147 | .it('should exit with code 101')
|
148 | })
|
149 | ```
|
150 |
|
151 | If you would like to run the same test without using `@oclif/test`:
|
152 |
|
153 | ```typescript
|
154 | import {Config, Errors, ux} from '@oclif/core'
|
155 | import {expect} from 'chai'
|
156 | import {SinonSandbox, createSandbox} from 'sinon'
|
157 |
|
158 | describe('non-fancy test', () => {
|
159 | let sandbox: SinonSandbox
|
160 | let config: Config
|
161 |
|
162 | beforeEach(async () => {
|
163 | sandbox = createSandbox()
|
164 | sandbox.stub(ux.write, 'stdout')
|
165 | config = await Config.load({root: process.cwd()})
|
166 | })
|
167 |
|
168 | afterEach(async () => {
|
169 | sandbox.restore()
|
170 | })
|
171 |
|
172 | it('should run command from the given directory', async () => {
|
173 | try {
|
174 | await config.runCommand('.')
|
175 | throw new Error('Expected CLIError to be thrown')
|
176 | } catch (error) {
|
177 | if (error instanceof Errors.CLIError) {
|
178 | expect(error.oclif.exit).to.equal(101)
|
179 | } else {
|
180 | throw error
|
181 | }
|
182 | }
|
183 | })
|
184 | })
|
185 | ```
|
186 |
|
187 | ### `.hook()`
|
188 |
|
189 | `.hook()` let's you test a hook in your CLI.
|
190 |
|
191 | ```typescript
|
192 | import {join} from 'node:path'
|
193 |
|
194 | import {expect, test} from '@oclif/test'
|
195 |
|
196 | const root = join(__dirname, 'fixtures/test-cli')
|
197 |
|
198 | describe('hooks', () => {
|
199 | test
|
200 | .loadConfig({root})
|
201 | .stdout()
|
202 | .hook('foo', {argv: ['arg']}, {root})
|
203 | .do((output) => expect(output.stdout).to.equal('foo hook args: arg\n'))
|
204 | .it('should run hook')
|
205 | })
|
206 | ```
|
207 |
|
208 | If you would like to run the same test without using `@oclif/test`:
|
209 |
|
210 | ```typescript
|
211 | import {Config, ux} from '@oclif/core'
|
212 | import {expect} from 'chai'
|
213 | import {SinonSandbox, SinonStub, createSandbox} from 'sinon'
|
214 |
|
215 | describe('non-fancy test', () => {
|
216 | let sandbox: SinonSandbox
|
217 | let config: Config
|
218 | let stdoutStub: SinonStub
|
219 |
|
220 | beforeEach(async () => {
|
221 | sandbox = createSandbox()
|
222 | stdoutStub = sandbox.stub(ux.write, 'stdout')
|
223 | config = await Config.load({root: process.cwd()})
|
224 | })
|
225 |
|
226 | afterEach(async () => {
|
227 | sandbox.restore()
|
228 | })
|
229 |
|
230 | it('should run hook', async () => {
|
231 | const {name} = await config.runHook('foo', {argv: ['arg']})
|
232 | expect(stdoutStub.calledWith('foo hook args: arg\n')).to.be.true
|
233 | })
|
234 | })
|
235 | ```
|