Path: blob/main/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts
5281 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as sinon from 'sinon';6import { DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';7import { SyncDescriptor, SyncDescriptor0 } from '../../common/descriptors.js';8import { GetLeadingNonServiceArgs, ServiceIdentifier, ServicesAccessor } from '../../common/instantiation.js';9import { InstantiationService, Trace } from '../../common/instantiationService.js';10import { ServiceCollection } from '../../common/serviceCollection.js';1112interface IServiceMock<T> {13id: ServiceIdentifier<T>;14service: any;15}1617const isSinonSpyLike = (fn: Function): fn is sinon.SinonSpy => fn && 'callCount' in fn;1819export class TestInstantiationService extends InstantiationService implements IDisposable, ServicesAccessor {2021private _servciesMap: Map<ServiceIdentifier<any>, any>;22private readonly _classStubs: Map<Function, any> = new Map();23private readonly _parentTestService: TestInstantiationService | undefined;2425constructor(private _serviceCollection: ServiceCollection = new ServiceCollection(), strict: boolean = false, parent?: InstantiationService, private _properDispose?: boolean) {26super(_serviceCollection, strict, parent);2728this._servciesMap = new Map<ServiceIdentifier<any>, any>();29if (parent instanceof TestInstantiationService) {30this._parentTestService = parent;31}32}3334public get<T>(service: ServiceIdentifier<T>): T {35return super._getOrCreateServiceInstance(service, Trace.traceCreation(false, TestInstantiationService));36}3738public set<T>(service: ServiceIdentifier<T>, instance: T): T {39return <T>this._serviceCollection.set(service, instance);40}4142public mock<T>(service: ServiceIdentifier<T>): T | sinon.SinonMock {43return <T>this._create(service, { mock: true });44}4546public stubInstance<T>(ctor: new (...args: any[]) => T, instance: Partial<T>): void {47this._classStubs.set(ctor, instance);48}4950protected _getClassStub(ctor: Function): unknown {51return this._classStubs.get(ctor) ?? this._parentTestService?._getClassStub(ctor);52}5354public override createInstance<T>(descriptor: SyncDescriptor0<T>): T;55public override createInstance<Ctor extends new (...args: any[]) => unknown, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;56public override createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: unknown[]): unknown {57const stub = this._getClassStub(ctorOrDescriptor as Function);58if (stub) {59return stub;60}61return super.createInstance(ctorOrDescriptor, ...rest);62}6364public stub<T>(service: ServiceIdentifier<T>, obj: Partial<NoInfer<T>> | Function): T;65public stub<T, V>(service: ServiceIdentifier<T>, obj: Partial<NoInfer<T>> | Function, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;66public stub<T, V>(service: ServiceIdentifier<T>, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;67public stub<T>(serviceIdentifier: ServiceIdentifier<T>, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub | sinon.SinonSpy {68const service = typeof arg2 !== 'string' ? arg2 : undefined;69const serviceMock: IServiceMock<any> = { id: serviceIdentifier, service: service };70const property = typeof arg2 === 'string' ? arg2 : arg3;71const value = typeof arg2 === 'string' ? arg3 : arg4;7273const stubObject = this._create(serviceMock, { stub: true }, service && !property);74if (property) {75if (stubObject[property]) {76if (stubObject[property].hasOwnProperty('restore')) {77stubObject[property].restore();78}79if (typeof value === 'function') {80const spy = isSinonSpyLike(value) ? value : sinon.spy(value);81stubObject[property] = spy;82return spy;83} else {84const stub = value ? sinon.stub().returns(value) : sinon.stub();85stubObject[property] = stub;86return stub;87}88} else {89stubObject[property] = value;90}91}92return stubObject;93}9495public stubPromise<T>(service?: ServiceIdentifier<T>, fnProperty?: string, value?: any): T | sinon.SinonStub;96public stubPromise<T, V>(service?: ServiceIdentifier<T>, ctor?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;97public stubPromise<T, V>(service?: ServiceIdentifier<T>, obj?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub;98public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub | sinon.SinonSpy {99arg3 = typeof arg2 === 'string' ? Promise.resolve(arg3) : arg3;100arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? Promise.resolve(arg4) : arg4;101return this.stub(arg1, arg2, arg3, arg4);102}103104public spy<T>(service: ServiceIdentifier<T>, fnProperty: string): sinon.SinonSpy {105const spy = sinon.spy();106this.stub(service, fnProperty, spy);107return spy;108}109110private _create<T>(serviceMock: IServiceMock<T>, options: SinonOptions, reset?: boolean): any;111private _create<T>(ctor: any, options: SinonOptions): any;112private _create(arg1: any, options: SinonOptions, reset: boolean = false): any {113if (this.isServiceMock(arg1)) {114const service = this._getOrCreateService(arg1, options, reset);115this._serviceCollection.set(arg1.id, service);116return service;117}118return options.mock ? sinon.mock(arg1) : this._createStub(arg1);119}120121private _getOrCreateService<T>(serviceMock: IServiceMock<T>, opts: SinonOptions, reset?: boolean): any {122const service: any = this._serviceCollection.get(serviceMock.id);123if (!reset && service) {124if (opts.mock && service['sinonOptions'] && !!service['sinonOptions'].mock) {125return service;126}127if (opts.stub && service['sinonOptions'] && !!service['sinonOptions'].stub) {128return service;129}130}131return this._createService(serviceMock, opts);132}133134private _createService(serviceMock: IServiceMock<any>, opts: SinonOptions): any {135serviceMock.service = serviceMock.service ? serviceMock.service : this._servciesMap.get(serviceMock.id);136const service = opts.mock ? sinon.mock(serviceMock.service) : this._createStub(serviceMock.service);137service['sinonOptions'] = opts;138return service;139}140141private _createStub(arg: any): any {142return typeof arg === 'object' ? arg : sinon.createStubInstance(arg);143}144145private isServiceMock(arg1: any): boolean {146return typeof arg1 === 'object' && arg1.hasOwnProperty('id');147}148149override createChild(services: ServiceCollection): TestInstantiationService {150return new TestInstantiationService(services, false, this);151}152153override dispose() {154sinon.restore();155if (this._properDispose) {156super.dispose();157}158}159}160161interface SinonOptions {162mock?: boolean;163stub?: boolean;164}165166export type ServiceIdCtorPair<T> = [id: ServiceIdentifier<T>, ctorOrInstance: T | (new (...args: any[]) => T)];167168export function createServices(disposables: DisposableStore, services: ServiceIdCtorPair<any>[]): TestInstantiationService {169const serviceIdentifiers: ServiceIdentifier<any>[] = [];170const serviceCollection = new ServiceCollection();171172const define = <T>(id: ServiceIdentifier<T>, ctorOrInstance: T | (new (...args: any[]) => T)) => {173if (!serviceCollection.has(id)) {174if (typeof ctorOrInstance === 'function') {175serviceCollection.set(id, new SyncDescriptor(ctorOrInstance as new (...args: any[]) => T));176} else {177serviceCollection.set(id, ctorOrInstance);178}179}180serviceIdentifiers.push(id);181};182183for (const [id, ctor] of services) {184define(id, ctor);185}186187const instantiationService = disposables.add(new TestInstantiationService(serviceCollection, true));188disposables.add(toDisposable(() => {189for (const id of serviceIdentifiers) {190const instanceOrDescriptor = serviceCollection.get(id);191if (typeof instanceOrDescriptor.dispose === 'function') {192instanceOrDescriptor.dispose();193}194}195}));196return instantiationService;197}198199200