如何测试调用Rest API的Oclif CLI及TypeScript命令代码?
测试基于Oclif的TypeScript CLI命令指南
我完全懂你的感受——用Mocha/Chai测Express API得心应手,但转到Oclif的TypeScript CLI测试时,官方那点简单示例根本不够用。别担心,咱们一步步拆解怎么测试你这个AdminCommand里的newuser功能,核心思路就是把所有外部依赖都mock掉,让测试只聚焦在命令本身的逻辑上。
1. 先搭建基础测试环境
首先得装齐必要的测试工具:Oclif官方的测试库、用于mock外部依赖的工具,还有你熟悉的Mocha/Chai。执行下面的命令安装:
npm install --save-dev mocha chai @oclif/test sinon nock mock-fs
2. 核心测试思路:隔离外部依赖
你的AdminCommand有几个关键的外部交互点,必须mock掉才能确保测试的独立性:
- 文件读取(读取token和证书文件)
cli-ux的anykey()交互(避免测试卡住等待用户输入)- Axios的POST API请求
- Bcrypt的哈希计算(可选mock,用来加速测试并验证调用逻辑)
3. 编写具体测试用例
下面是针对newuser分支的完整测试示例,创建test/commands/admin.test.ts文件:
import { expect } from 'chai'; import * as sinon from 'sinon'; import * as mockFs from 'mock-fs'; import * as nock from 'nock'; import { test } from '@oclif/test'; import { AdminCommand } from '../../src/commands/admin'; import * as bcrypt from 'bcrypt'; describe('admin command (newuser flag)', () => { // 测试前初始化所有mock beforeEach(() => { // 模拟文件系统,替代真实的token和证书文件 mockFs({ 'softeng19bAPI.token': 'test-token-123', 'ca-crt.pem': 'fake-cert-content' }); // Mock cli.anykey(),让它直接resolve不用等待用户操作 sinon.stub(require('cli-ux'), 'anykey').resolves(); // Mock bcrypt.hashSync,返回固定哈希值方便断言 sinon.stub(bcrypt, 'hashSync').returns('hashed-test-password'); }); // 测试后清理所有mock,避免影响其他测试 afterEach(() => { mockFs.restore(); sinon.restore(); nock.cleanAll(); }); it('should send correct POST request when all required newuser flags are provided', async () => { // 拦截目标API的POST请求,验证请求内容 const apiMock = nock('https://localhost:8765') .post('/energy/api/Admin/users', { username: 'test-user', passw: 'test-pass', email: 'test@example.com', quota: '100' }) .reply(201); // 运行admin命令并传入所需flags await test .command(['admin', '--newuser', 'test-user', '--passw', 'test-pass', '--email', 'test@example.com', '--quota', '100']) .do(() => { // 验证API请求是否被正确触发 expect(apiMock.isDone()).to.be.true; // 验证bcrypt是否按预期调用 expect(bcrypt.hashSync.calledWith('test-pass', 10)).to.be.true; }) .exit(0) // 命令正常退出的状态码为0 .run(); }); it('should NOT send POST request if required newuser flags are missing', async () => { const apiMock = nock('https://localhost:8765') .post('/energy/api/Admin/users') .reply(201); // 只传入部分flags,故意不满足依赖条件 await test .command(['admin', '--newuser', 'test-user', '--passw', 'test-pass']) .do(() => { // 验证API请求没有被触发 expect(apiMock.isDone()).to.be.false; }) .exit(0) .run(); }); });
4. 关键测试点说明
- 文件系统Mock:用
mock-fs模拟token和证书文件,彻底摆脱对真实文件的依赖,保证测试环境一致。 - 交互Mock:
cli.anykey()会暂停命令等待用户输入,测试时必须mock掉让它直接resolve,否则测试会卡住。 - API请求拦截:
nock可以拦截Axios的请求,不仅能验证请求的URL、参数是否正确,还能模拟不同的响应状态(比如400错误)来测试命令的错误处理逻辑。 - Oclif测试工具:
@oclif/test提供的command()方法可以直接运行CLI命令,还能捕获命令的输出、退出状态等关键信息。
5. 补充:测试错误场景
比如测试API返回错误的情况,可以加这个用例:
it('should handle API error when creating user', async () => { const apiMock = nock('https://localhost:8765') .post('/energy/api/Admin/users') .reply(400, { error: 'Username already exists' }); await test .command(['admin', '--newuser', 'test-user', '--passw', 'test-pass', '--email', 'test@example.com', '--quota', '100']) .catch(error => { expect(error.message).to.include('400'); }) .run(); });
内容的提问来源于stack exchange,提问作者user12610369
相关产品推荐
相关产品推荐

