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.
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]
})
);
})
})