看起来你正在变成间谍实例。对吗?
无论如何,我认为这里的简单解决方案是使用映射类型来更改组件的类型。
例子:
type SpyifyFunctions<T> = {
// If jasmine.Spy can be used with type arguments, you could supply that here, too
[K in keyof T]: T[K] extends Function ? jasmine.Spy : T[K]
}
describe('ParentComponent', () => {
let component: SpyifyFunctions<ParentComponent>;
let fixture: ComponentFixture<ParentComponent>;
/**
* This function is the "marker" for the custom typescript transformer.
* With this line the typescript transformer "knows" that it has to adjust the class ParentComponent.
*
* I don't know if this is possible but it would be cool if after the function "ttransformer" the type for ParentComponent would be adjusted.
* With adjusted I mean that e.g. each class property of type number is now reflected as type string (as explained in the issue description above)
*/
ttransformer(ParentComponent);
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ParentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ParentComponent);
component = fixture.componentInstance; // If necessary, cast here (as SpyifyFunctions<ParentComponent>)
fixture.detectChanges();
});
it('test it', () => {
component.ttransformerTest.and.returnValue('Transformer Test');
expect(component.ttransformerTest).toHaveBeenCalled();
});
});
编辑
这个问题需要了解 TypeScript 中的类型系统。起初,如果来自其他语言,可能会有点难以理解。
我们需要首先认识到类型在编译期间只存在。它们在那里是为了告诉您的 IDE 或编译器应该是什么。这是通过提供错误并为您的 IDE 提供智能感知来帮助您。当代码实际运行时,类型根本不存在!了解这一点的一个好方法是去打字稿游乐场并查看输出的 javascript(在右侧面板上)
看看this example
点击上面的链接,看看这段代码是如何去除所有类型信息的。
type Spy<TFunction> = { fn: TFunction }
class ParentComponent {
// We assume an external transformer will change ttransformerTest from a method to a property with the shape { fn: (original method function) }
ttransformerTest(): string { return '' }
p!: string
}
type SpyifyFunctions<T> = {
[K in keyof T]: T[K] extends Function ? Spy<T[K]> : T[K]
}
// Here we're specifically telling TypeScript what Type the component is, so that we don't get errors trying to use component.fn
const component = new ParentComponent() as unknown as SpyifyFunctions<ParentComponent>;
component.ttransformerTest.fn() // <-- No errors, because TypeScript recognizes this as the proper type for component
一个有助于理解的关键点是一旦分配给引用,就无法更新分配给它的类型。
请记住,类型是在运行转换器之前进行遍历和分配的。信不信由你,这很好。否则会很混乱。
考虑到这一点:
- 我们现在知道转换器不会影响我们的 IDE 或编译器中的错误,因为它们发生在已经分析代码之后。
- 由于 JavaScript 端由转换器处理,现在我们需要找到一种方法以您的 IDE 可以识别的方式“转换”实际类型。
这是一种您可以使用一些辅助类型来做到这一点的方法。考虑到您的用例,这可能是完成您想做的事情的最佳途径。
/* ************************************************************************************* */
// type-transformers.ts
/* ************************************************************************************* */
type Spy<TFunction> = { fn: TFunction }
type SpyifyFunctions<T> = {
[K in keyof T]: T[K] extends Function ? Spy<T[K]> : T[K]
}
export type MakeSpyableClass<T extends new (...args: any[]) => any> =
T extends new (...args: infer Args) => any ?
new (...args: Args) => SpyifyFunctions<InstanceType<T>> : never;
/* ************************************************************************************* */
// ParentComponent.ts
/* ************************************************************************************* */
import { MakeSpyableClass } from './type-transformers.ts'
// Note that we do not export this class, directly, since we need TS to infer its type before
// we "transform" it as an export
class ParentComponentClass {
// We assume an external transformer will change ttransformerTest from a method to a
// property with the shape { fn: (original method function) }
ttransformerTest(): string { return '' }
p!: string
}
// Here is where we create a new exported reference to the class with a specific type that we
// assign We cast to unknown first, so TypeScript doesn't complain about the function shapes
// not matching (remember, it doesn't know about your tranformer)
export const ParentComponent =
ParentComponentClass as unknown as MakeSpyableClass<typeof ParentComponentClass>
/* ************************************************************************************* */
// example.ts
/* ************************************************************************************* */
import { ParentComponent } from './ParentComponent.ts'
const component = new ParentComponent();
component.ttransformerTest.fn() // <-- Recognizes type as Spy instance
See it in Playground
注意事项
您的方法很可能会简单得多。它也可能会产生一些不好的后果,所以我添加了一些可能会有所帮助的注释。
- 看起来您的类是在测试环境之外定义和使用的。
- 您似乎还想在测试期间自动将所有功能变成间谍。
如果是这种情况,使用转换器将所有功能普遍变成间谍是不好的做法。您不会希望将 jasmine 代码实现到在测试环境之外运行的东西中。
相反,一种更好(也更简单)的方法是编写一个函数,枚举类实例的属性描述符并使其返回类型使用映射类型,以便您的测试了解发生了什么。
Transformer 比您需要的要复杂得多,而且肯定不是一个好主意,仅用于测试期间需要的东西。
我看到你有TestBed.createComponent。假设 createComponent 存在于测试空间中,这可能就是我要输入的逻辑:
- 迭代 Object.getOwnPropertyDescriptors() -> 如果是方法或函数,则使用该方法/函数的 Jasmine spy 更新对象
- 函数的返回类型应该使用映射类型帮助器
有点像
type SpyifyFunctions<T> = {
[K in keyof T]: T[K] extends Function ? Spy<T[K]> : T[K]
}
createComponent<T extends new (...args: any[]) => any>(component: T): SpyifyFunctions<InstanceType<T>> {
// Create new component
// Iterate descriptors and replace with spies
// return component
}
你想要最少的工作量——当我告诉你的时候相信我,一个变压器几乎总是不是正确的路线。 ?