vitestoclif

oclif runCommand vitest test fails with command X not found


I have a few commands with oclif whose tests fail with vitest but pass with jest. I believe something is off with the mocking. This is the structure:

src/
   commands/
       foo-cmd.ts
       tests/
          foo-cmd.test.ts
   controllers/
       foo-ctrl.ts

The foo-cmd.ts command uses async run() from oclif/command and calls the default export of the controller: await foo(props);.

The test is like:

import foo from '../../controllers/foo-ctrl';
import { runCommand } from '@oclif/test';

vi.mock('../../controllers/foo-ctrl');

describe('foo command', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('runs foo command', async () => {
    const res = await runCommand(
      'foo ./src/... [args reducted]'
    );
    console.log('+++ res', res);
    expect(res.stdout).toContain('Updates foo');
    expect(foo).toHaveBeenCalledWith(
      expect.objectContaining({
        ...props [reducted]
      })
    );
  });
//...

The actual error is shown in the res console.log of the test and is:

+++ res {
  error: CLIError: command foo not found
    at Config.runCommand
    // ...
    oclif: { exit: 2 },
    // ...
 stderr: '',
 stdout: ''
}

My vitest.config.ts has:

test: {
    globals: true,
    environment: 'node',
    watch: false,
    disableConsoleIntercept: true 
  },

UPDATE: Even using the simple example of this repo: https://github.com/jpshack-at-palomar/oclif-with-vitest/blob/main/test/commands/hello/world.test.ts doesn't work.


Solution

  • I fixed it by passing our config to the method!

    import { runCommand } from '@oclif/test';
    import { Config } from '@oclif/core';
    import { join } from 'path';
    import foo from '../../foo';
    
    vi.mock('../../foo-ctrl', () => { default: vi.fn() });
    
    describe('foo command', () => ({
      const config = await Config.load(join(__dirname, '../../config.ts'));
    
      afterEach(() => {
        vi.clearAllMocks();
      });
    
      it('passes', () => ({
          const { stdout } = await runCommand(
            'foo ./example.yaml  --id abc',
            config
          );
          expect(stdout).toContain('Updates foo');
          expect(foo).toHaveBeenCalledWith(
             expect.objectContaining({
              ...props [reducted]
             })
          );
      })
    })