Problem
The environment managers (Conda, Pyenv, Pipenv, Poetry, Venv, SysPython) lack comprehensive unit tests because testing them is complex. The main barrier is the NativePythonFinder dependency which:
- Spawns an external native binary (
pet.exe/pet) via JSON-RPC
- Requires real file system operations
- Depends on actual Python installations being present
Currently, only helper utilities within managers have tests (e.g., pipUtils, venvUtils, installArgs), but the manager classes themselves (CondaEnvManager, VenvManager, SysPythonManager, PyenvManager, PipenvManager, PoetryManager) are untested.
Current State
- Tested: Helper functions in
src/test/managers/builtin/ (pipUtils, venvUtils, etc.)
- Untested: Core manager classes in
src/managers/*/
- Limited mocking:
NativePythonFinder is only partially mocked in interpreterSelection.unit.test.ts for the resolve() method
Proposed Solution
1. Create a Mock/Fake NativePythonFinder
Create a reusable MockNativePythonFinder class in src/test/mocks/mockNativePythonFinder.ts:
import * as sinon from 'sinon';
import { NativePythonFinder, NativeInfo, NativeEnvInfo, NativePythonEnvironmentKind } from '../../managers/common/nativePythonFinder';
import { Uri } from 'vscode';
export interface MockNativeFinderOptions {
environments?: NativeEnvInfo[];
managers?: { tool: string; executable: string; version?: string }[];
resolveResults?: Map<string, NativeEnvInfo>;
}
export function createMockNativePythonFinder(options: MockNativeFinderOptions = {}): NativePythonFinder {
const { environments = [], managers = [], resolveResults = new Map() } = options;
return {
refresh: sinon.stub().callsFake(async (_hardRefresh: boolean, filterOptions?: NativePythonEnvironmentKind | Uri[]): Promise<NativeInfo[]> => {
// Filter by kind if specified
if (typeof filterOptions === 'string') {
return environments.filter(e => e.kind === filterOptions);
}
return [...environments, ...managers];
}),
resolve: sinon.stub().callsFake(async (executable: string): Promise<NativeEnvInfo> => {
const result = resolveResults.get(executable);
if (!result) {
throw new Error(`Unknown executable: ${executable}`);
}
return result;
}),
dispose: sinon.stub(),
};
}
2. Create Test Fixture Data
Add src/test/fixtures/nativeFinderData.ts with predefined environment data:
import { NativeEnvInfo, NativePythonEnvironmentKind } from '../../managers/common/nativePythonFinder';
export const MOCK_CONDA_ENVS: NativeEnvInfo[] = [
{
displayName: 'base',
name: 'base',
executable: '/opt/conda/bin/python',
kind: NativePythonEnvironmentKind.conda,
version: '3.11.0',
prefix: '/opt/conda',
manager: { tool: 'conda', executable: '/opt/conda/bin/conda', version: '23.5.0' },
},
{
displayName: 'myenv',
name: 'myenv',
executable: '/opt/conda/envs/myenv/bin/python',
kind: NativePythonEnvironmentKind.conda,
version: '3.10.0',
prefix: '/opt/conda/envs/myenv',
manager: { tool: 'conda', executable: '/opt/conda/bin/conda', version: '23.5.0' },
},
];
export const MOCK_VENV_ENVS: NativeEnvInfo[] = [
{
displayName: '.venv',
name: '.venv',
executable: '/workspace/.venv/bin/python',
kind: NativePythonEnvironmentKind.venv,
version: '3.12.0',
prefix: '/workspace/.venv',
},
];
// ... similar for pyenv, pipenv, poetry
3. Create Mock PythonEnvironmentApi
The managers also depend on PythonEnvironmentApi. Add src/test/mocks/mockPythonApi.ts:
export function createMockPythonEnvironmentApi(): Partial<PythonEnvironmentApi> {
return {
getEnvironmentManager: sinon.stub(),
getPackageManager: sinon.stub(),
registerEnvironmentManager: sinon.stub().returns({ dispose: sinon.stub() }),
registerPackageManager: sinon.stub().returns({ dispose: sinon.stub() }),
// ... other minimal stubs
};
}
4. Simplify Manager Construction
Consider adding a factory or builder pattern for managers to make dependency injection clearer:
// In the manager files, add a factory function for testing
export function createCondaEnvManager(
nativeFinder: NativePythonFinder,
api: PythonEnvironmentApi,
log: LogOutputChannel,
): CondaEnvManager {
return new CondaEnvManager(nativeFinder, api, log);
}
5. Example Test Structure
// src/test/managers/conda/condaEnvManager.unit.test.ts
import assert from 'node:assert';
import * as sinon from 'sinon';
import { createMockNativePythonFinder } from '../../mocks/mockNativePythonFinder';
import { createMockPythonEnvironmentApi } from '../../mocks/mockPythonApi';
import { createMockLogOutputChannel } from '../../mocks/helper';
import { MOCK_CONDA_ENVS } from '../../fixtures/nativeFinderData';
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
suite('CondaEnvManager', () => {
let manager: CondaEnvManager;
let mockFinder: NativePythonFinder;
setup(() => {
mockFinder = createMockNativePythonFinder({
environments: MOCK_CONDA_ENVS,
});
const mockApi = createMockPythonEnvironmentApi();
const mockLog = createMockLogOutputChannel();
manager = new CondaEnvManager(mockFinder, mockApi as PythonEnvironmentApi, mockLog);
});
teardown(() => {
sinon.restore();
manager.dispose();
});
test('getEnvironments returns all conda environments', async () => {
await manager.initialize();
const envs = await manager.getEnvironments('all');
assert.strictEqual(envs.length, 2);
// ... assertions
});
});
Acceptance Criteria
Additional Simplifications
-
Abstract child process spawning: The *Utils.ts files (e.g., condaUtils.ts) spawn child processes. These could be abstracted through wrapper functions in common/childProcess.apis.ts for easier mocking.
-
File system operations: Use the existing workspace.fs.apis wrappers consistently across managers for easier stubbing.
-
Settings access: Ensure all settings are accessed through workspace.apis wrappers rather than direct vscode.workspace.getConfiguration() calls.
Related Files
src/managers/common/nativePythonFinder.ts - The interface to mock
src/test/mocks/helper.ts - Existing mock helpers
src/test/features/interpreterSelection.unit.test.ts - Example of partial NativePythonFinder mocking
src/managers/*/ - All manager implementations needing tests
Problem
The environment managers (Conda, Pyenv, Pipenv, Poetry, Venv, SysPython) lack comprehensive unit tests because testing them is complex. The main barrier is the
NativePythonFinderdependency which:pet.exe/pet) via JSON-RPCCurrently, only helper utilities within managers have tests (e.g.,
pipUtils,venvUtils,installArgs), but the manager classes themselves (CondaEnvManager,VenvManager,SysPythonManager,PyenvManager,PipenvManager,PoetryManager) are untested.Current State
src/test/managers/builtin/(pipUtils, venvUtils, etc.)src/managers/*/NativePythonFinderis only partially mocked ininterpreterSelection.unit.test.tsfor theresolve()methodProposed Solution
1. Create a Mock/Fake NativePythonFinder
Create a reusable
MockNativePythonFinderclass insrc/test/mocks/mockNativePythonFinder.ts:2. Create Test Fixture Data
Add
src/test/fixtures/nativeFinderData.tswith predefined environment data:3. Create Mock PythonEnvironmentApi
The managers also depend on
PythonEnvironmentApi. Addsrc/test/mocks/mockPythonApi.ts:4. Simplify Manager Construction
Consider adding a factory or builder pattern for managers to make dependency injection clearer:
5. Example Test Structure
Acceptance Criteria
src/test/mocks/mockNativePythonFinder.tswith full interface implementationsrc/test/fixtures/nativeFinderData.tswith realistic test datasrc/test/mocks/mockPythonApi.tsfor API dependencyCondaEnvManagerVenvManagerSysPythonManagerPyenvManagerPipenvManagerPoetryManagerAdditional Simplifications
Abstract child process spawning: The
*Utils.tsfiles (e.g.,condaUtils.ts) spawn child processes. These could be abstracted through wrapper functions incommon/childProcess.apis.tsfor easier mocking.File system operations: Use the existing
workspace.fs.apiswrappers consistently across managers for easier stubbing.Settings access: Ensure all settings are accessed through
workspace.apiswrappers rather than directvscode.workspace.getConfiguration()calls.Related Files
src/managers/common/nativePythonFinder.ts- The interface to mocksrc/test/mocks/helper.ts- Existing mock helperssrc/test/features/interpreterSelection.unit.test.ts- Example of partial NativePythonFinder mockingsrc/managers/*/- All manager implementations needing tests