为第三方 cli 包编写单元测试用例

Writing unit test case for third party cli package

我有一个使用 yargs 构建的基本 CLI 程序。我能够涵盖应用程序中导出函数的测试用例。

正如您在下面看到的那样,测试覆盖率并未从第 12-18 行完成。我们如何为像 yargs 这样的第三方包编写单元测试覆盖率?

index.js

const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const greet = (name) => {
  return `Welcome ${name}`;
};
yargs(hideBin(process.argv)).command(
  'run [name]',
  'print name',
  (yargs) => {
    yargs.positional('name', { describe: 'Your name', type: 'string' });
  },
  (args) => {
    const { name } = args;

    const greetMsg = greet(name);

    console.log(greetMsg);
  }
).argv;

module.exports = { greet };

index.test.js

const { greet } = require('./index')

describe.only('greeting', () => {
  it('greet', async () => {
    const greetMsg = greet('test')

    expect(greetMsg).toBe('Welcome test')
  })
})

测试覆盖率

PASS  ./index.test.js
greeting
  ✓ greet (5 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   63.64 |      100 |   33.33 |   63.64 |                   
index.js  |   63.64 |      100 |   33.33 |   63.64 | 12-18            
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.316 s, estimated 2 s
Ran all test suites.

您可以使用 jest.doMock(moduleName, factory, options) to mock yargs and yargs/helpers module. Since the yargs function in the module scope will be executed when requiring the module. You need to use jest.resetModules() 重置模块注册表 - 在需要 index.js 模块之前每个测试用例的所有必需模块的缓存。

This is useful to isolate modules where local state might conflict between tests.

例如

index.js:

const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');

const greet = (name) => {
  return `Welcome ${name}`;
};
yargs(hideBin(process.argv)).command(
  'run [name]',
  'print name',
  (yargs) => {
    yargs.positional('name', { describe: 'Your name', type: 'string' });
  },
  (args) => {
    const { name } = args;

    const greetMsg = greet(name);

    console.log(greetMsg);
  }
).argv;

module.exports = { greet };

index.test.js:

describe.only('greeting', () => {
  beforeEach(() => {
    jest.resetModules();
  });
  it('greet', () => {
    const { greet } = require('./index');
    const greetMsg = greet('test');
    expect(greetMsg).toBe('Welcome test');
  });

  it('should pass', () => {
    jest.doMock('yargs');
    jest.doMock('yargs/helpers');
    const yargs = require('yargs');
    const { hideBin } = require('yargs/helpers');
    const mArgv = {
      command: jest.fn().mockImplementation(function (command, description, builder, handler) {
        builder(this);
        handler({ name: 'teresa teng' });
        return this;
      }),
      argv: {},
      positional: jest.fn(),
    };
    yargs.mockReturnValueOnce(mArgv);
    require('./');
    expect(hideBin).toBeCalled();
    expect(yargs).toBeCalled();
    expect(mArgv.positional).toBeCalledWith('name', { describe: 'Your name', type: 'string' });
  });
});

测试结果:

 PASS  examples/67830954/index.test.js (7.17 s)
  greeting
    ✓ greet (355 ms)
    ✓ should pass (23 ms)

  console.log
    Welcome teresa teng

      at examples/67830954/index.js:18:13

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.js |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        7.668 s, estimated 8 s