You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 19:23:11