如何根据条件返回属性类型?

时间:2017-09-30 21:25:26

标签: typescript typescript2.0

在我的jasmine-auto-spies项目中,我有the following lines -

export type Spy<T> = T & {
  [k in keyof T]: AsyncSpyFunction;
}

export interface AsyncSpyFunction extends jasmine.Spy {
  (...params: any[]): any;
  and: AsyncSpyFunctionAnd
}

export interface AsyncSpyFunctionAnd extends jasmine.SpyAnd {
  nextWith(value: any): void;
  nextWithError(value: any): void;
  resolveWith(value: any): void;
  rejectWith(value: any): void;
}

我想要实现的是将一个类的所有功能都变成间谍。

所以我能做到:

let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);

获取一个对象,它是类实例 AND 的间谍。

所以当我调用其中一种方法时 -

myServiceSpy.doSomething();

我也可以这样设置 -

myServiceSpy.doSomething.and.returnValue('someVal');

但是......我错过了两件事:

1。忽略getter和setter函数的能力

就类型而言,我想忽略getter和setter,因为它们很容易通过设置属性来嘲笑。

目前,如果我尝试设置一个具有类似

的setter函数的属性,TypeScript会抱怨
MyService{
    set url(value:string){
        this._url = value;
    }
}

let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);

在我的测试中,我尝试覆盖它 -

myServiceSpy.url = 'test';

我收到此错误 -

Type '"test"' is not assignable to type 'string & AsyncSpyFunction'.

所以我希望能够在类型定义中忽略setter和getter。

2。根据原始返回类型返回不同的间谍类型

我希望能够在原始函数返回Observable时返回一个ObservableSpy。

同样适用于Promise和其他异步类型。

每个人都有自己独特的方法。

目前我只使用了catch all all类型,它具有所有可能实现的spy方法,但我更喜欢更具体。

所以,让我说我有以下课程 -

class MyService{
    getUsers():Observable<User[]> {
        return Observable.of(users);
    }

    getToken():Promise<Token> {
        return Promise.resolve(token);
    }
}

在我的测试中,我正在设置 -

myServiceSpy.getUsers.and.nextWith(fakeUsers);
myServiceSpy.getToken.and.resolveWith(fakeToken);

我希望方法nextWith仅出现在返回Observables

的方法中

resovleWith只出现在返回承诺的方法中。

这可能以某种方式配置吗?

1 个答案:

答案 0 :(得分:2)

随着typescript 2.8的发布,您可以使用映射的条件类型:

import { Observable } from "rxjs";

export type Spy<T> = { [k in keyof T]: AddSpyTypes<T[k]> };

export type AddSpyTypes<T> = T extends (...args: any[]) => any
  ? AddSpyByReturnTypes<T>
  : T;

export interface PromiseSpy<T> {
  resolveWith(value: T): void;
  rejectWith(value: any): void;
}

export interface ObservableSpy<T> {
  nextWith(value: T): void;
  nextWithError(value: any): void;
}

export type AddSpyOnFunction<T extends (...args: any[]) => R, R> = T & {
  and: jasmine.Spy;
};

export type AddSpyOnPromise<T extends Promise<any>> = T & {
  and: PromiseSpy<Unpacked<T>>;
};
export type AddSpyOnObservable<T extends Observable<any>> = T & {
  and: ObservableSpy<Unpacked<T>>;
};

// Wrap the return type of the given function type with the appropriate spy methods
export type AddSpyByReturnTypes<
  TF extends (...args: any[]) => any
> = TF extends (...args: any[]) => infer TR // returnes a function
  ? TR extends (...args: any[]) => infer R2
    ? AddSpyOnFunction<TR, R2> // returnes a Promise
    : TR extends Promise<any>
      ? AddSpyOnPromise<TR> // returnes an Observable
      : TR extends Observable<any> ? AddSpyOnObservable<TR> : TF
  : never;

//github.com/Microsoft/TypeScript/issues/21705#issue-294964744
export type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
    ? U
    : T extends Promise<infer U> ? U : T extends Observable<infer U> ? U : T;

陈旧而陈旧的答案:

<击> *这不是一个有效的解决方案,而是迈向*

的一步

我的直接想法是向TS提供更多数据。 这远非完美,我希望有可能通过TS获得更多

另一个问题是,我们不能(?)真正推断出resolveWith / rejectWith的类型。

如果您将在P和PO界面上提供道具的类型,而不仅仅是道具名称,我们可以使用它们。

    // p is interface with the the promise props, PO is an interface with the observer props 
export type Spy<T, P, PO> = T & {
  [k in keyof P]: AsyncSpyFunctionAnd<P[k]>;
} & {
  [k in keyof PO]: AsyncSpyFunctionAndObserver<PO[k]>;
}

export interface AsyncSpyFunction<T> extends jasmine.Spy {
  (...params: any[]): any;
  and: AsyncSpyFunctionAnd<T>;
}

export interface AsyncSpyFunctionAnd<T> extends jasmine.SpyAnd {
  resolveWith(value: T): void;
  rejectWith(value: any): void;
}

export interface AsyncSpyFunctionAndObserver<T> extends jasmine.SpyAnd {
  nextWith(value: T): void;
  nextWithError(value: any): void;
}

在打字稿操场上:link