|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +/** |
| 5 | + * Centralized mock utilities for testing testController components. |
| 6 | + * Re-use these helpers across multiple test files for consistency. |
| 7 | + */ |
| 8 | + |
| 9 | +import * as sinon from 'sinon'; |
| 10 | +import * as typemoq from 'typemoq'; |
| 11 | +import { TestItem, TestItemCollection, TestRun, Uri } from 'vscode'; |
| 12 | +import { IPythonExecutionFactory } from '../../../client/common/process/types'; |
| 13 | +import { ITestDebugLauncher } from '../../../client/testing/common/types'; |
| 14 | +import { ProjectAdapter } from '../../../client/testing/testController/common/projectAdapter'; |
| 15 | +import { ProjectExecutionDependencies } from '../../../client/testing/testController/common/projectTestExecution'; |
| 16 | +import { TestProjectRegistry } from '../../../client/testing/testController/common/testProjectRegistry'; |
| 17 | +import { ITestExecutionAdapter, ITestResultResolver } from '../../../client/testing/testController/common/types'; |
| 18 | + |
| 19 | +/** |
| 20 | + * Creates a mock TestItem with configurable properties. |
| 21 | + * @param id - The unique ID of the test item |
| 22 | + * @param uriPath - The file path for the test item's URI |
| 23 | + * @param children - Optional array of child test items |
| 24 | + */ |
| 25 | +export function createMockTestItem(id: string, uriPath: string, children?: TestItem[]): TestItem { |
| 26 | + const childMap = new Map<string, TestItem>(); |
| 27 | + children?.forEach((c) => childMap.set(c.id, c)); |
| 28 | + |
| 29 | + const mockChildren: TestItemCollection = { |
| 30 | + size: childMap.size, |
| 31 | + forEach: (callback: (item: TestItem, collection: TestItemCollection) => void) => { |
| 32 | + childMap.forEach((item) => callback(item, mockChildren)); |
| 33 | + }, |
| 34 | + get: (itemId: string) => childMap.get(itemId), |
| 35 | + add: () => {}, |
| 36 | + delete: () => {}, |
| 37 | + replace: () => {}, |
| 38 | + [Symbol.iterator]: function* () { |
| 39 | + for (const [key, value] of childMap) { |
| 40 | + yield [key, value] as [string, TestItem]; |
| 41 | + } |
| 42 | + }, |
| 43 | + } as TestItemCollection; |
| 44 | + |
| 45 | + return ({ |
| 46 | + id, |
| 47 | + uri: Uri.file(uriPath), |
| 48 | + children: mockChildren, |
| 49 | + label: id, |
| 50 | + canResolveChildren: false, |
| 51 | + busy: false, |
| 52 | + tags: [], |
| 53 | + range: undefined, |
| 54 | + error: undefined, |
| 55 | + parent: undefined, |
| 56 | + } as unknown) as TestItem; |
| 57 | +} |
| 58 | + |
| 59 | +/** |
| 60 | + * Creates a mock TestItem without a URI. |
| 61 | + * Useful for testing edge cases where test items have no associated file. |
| 62 | + * @param id - The unique ID of the test item |
| 63 | + */ |
| 64 | +export function createMockTestItemWithoutUri(id: string): TestItem { |
| 65 | + return ({ |
| 66 | + id, |
| 67 | + uri: undefined, |
| 68 | + children: ({ size: 0, forEach: () => {} } as unknown) as TestItemCollection, |
| 69 | + label: id, |
| 70 | + } as unknown) as TestItem; |
| 71 | +} |
| 72 | + |
| 73 | +export interface MockProjectAdapterConfig { |
| 74 | + projectPath: string; |
| 75 | + projectName: string; |
| 76 | + pythonPath?: string; |
| 77 | + testProvider?: 'pytest' | 'unittest'; |
| 78 | +} |
| 79 | + |
| 80 | +export type MockProjectAdapter = ProjectAdapter & { executionAdapterStub: sinon.SinonStub }; |
| 81 | + |
| 82 | +/** |
| 83 | + * Creates a mock ProjectAdapter for testing project-based test execution. |
| 84 | + * @param config - Configuration object with project details |
| 85 | + * @returns A mock ProjectAdapter with an exposed executionAdapterStub for verification |
| 86 | + */ |
| 87 | +export function createMockProjectAdapter(config: MockProjectAdapterConfig): MockProjectAdapter { |
| 88 | + const runTestsStub = sinon.stub().resolves(); |
| 89 | + const executionAdapter: ITestExecutionAdapter = ({ |
| 90 | + runTests: runTestsStub, |
| 91 | + } as unknown) as ITestExecutionAdapter; |
| 92 | + |
| 93 | + const resultResolverMock: ITestResultResolver = ({ |
| 94 | + vsIdToRunId: new Map<string, string>(), |
| 95 | + runIdToVSid: new Map<string, string>(), |
| 96 | + runIdToTestItem: new Map<string, TestItem>(), |
| 97 | + detailedCoverageMap: new Map(), |
| 98 | + resolveDiscovery: () => Promise.resolve(), |
| 99 | + resolveExecution: () => {}, |
| 100 | + } as unknown) as ITestResultResolver; |
| 101 | + |
| 102 | + const adapter = ({ |
| 103 | + projectUri: Uri.file(config.projectPath), |
| 104 | + projectName: config.projectName, |
| 105 | + workspaceUri: Uri.file(config.projectPath), |
| 106 | + testProvider: config.testProvider ?? 'pytest', |
| 107 | + pythonEnvironment: config.pythonPath |
| 108 | + ? { |
| 109 | + execInfo: { run: { executable: config.pythonPath } }, |
| 110 | + } |
| 111 | + : undefined, |
| 112 | + pythonProject: { |
| 113 | + name: config.projectName, |
| 114 | + uri: Uri.file(config.projectPath), |
| 115 | + }, |
| 116 | + executionAdapter, |
| 117 | + discoveryAdapter: {} as any, |
| 118 | + resultResolver: resultResolverMock, |
| 119 | + isDiscovering: false, |
| 120 | + isExecuting: false, |
| 121 | + // Expose the stub for testing |
| 122 | + executionAdapterStub: runTestsStub, |
| 123 | + } as unknown) as MockProjectAdapter; |
| 124 | + |
| 125 | + return adapter; |
| 126 | +} |
| 127 | + |
| 128 | +/** |
| 129 | + * Creates mock dependencies for project test execution. |
| 130 | + * @returns An object containing mocked ProjectExecutionDependencies |
| 131 | + */ |
| 132 | +export function createMockDependencies(): ProjectExecutionDependencies { |
| 133 | + return { |
| 134 | + projectRegistry: typemoq.Mock.ofType<TestProjectRegistry>().object, |
| 135 | + pythonExecFactory: typemoq.Mock.ofType<IPythonExecutionFactory>().object, |
| 136 | + debugLauncher: typemoq.Mock.ofType<ITestDebugLauncher>().object, |
| 137 | + }; |
| 138 | +} |
| 139 | + |
| 140 | +/** |
| 141 | + * Creates a mock TestRun with common setup methods. |
| 142 | + * @returns A TypeMoq mock of TestRun |
| 143 | + */ |
| 144 | +export function createMockTestRun(): typemoq.IMock<TestRun> { |
| 145 | + const runMock = typemoq.Mock.ofType<TestRun>(); |
| 146 | + runMock.setup((r) => r.started(typemoq.It.isAny())); |
| 147 | + runMock.setup((r) => r.passed(typemoq.It.isAny(), typemoq.It.isAny())); |
| 148 | + runMock.setup((r) => r.failed(typemoq.It.isAny(), typemoq.It.isAny(), typemoq.It.isAny())); |
| 149 | + runMock.setup((r) => r.skipped(typemoq.It.isAny())); |
| 150 | + runMock.setup((r) => r.end()); |
| 151 | + return runMock; |
| 152 | +} |
0 commit comments