如何键入运行或模拟另一个类型化方法的函数

问题描述

我正在尝试为应该是相当简单的 Typescript 案例建立类型化,但某些内容没有正确绑定。我有返回类型化反应的操作。补充框架中的一个动作是一个执行者。此函数接受一个 Action 并返回一个相应的 Reaction(可能是模拟 Action)。

但是,我在 Performer 的模拟逻辑中使用的相等性测试和类型谓词(检查是否应该拦截和模拟 Action)似乎没有正确耦合到 Performer 本身的类型,导致编译错误

谁能发现需要哪些额外的通用绑定来消除我面临的编译问题?

我创建了一个最小的复制品,如下所示。 MOCK_PERFORM 的声明会引发编译错误 'Reaction' Could be instantiated with an arbitrary type which Could be unrelated to 'string',好像编译器无法利用类型谓词来指示“字符串”是来自 Action 的合法反应类型。

/** DEFINE ACTION AND PERFORMER */

//Defines act() - a method which produces a Reaction
export interface Action<Reaction> {
  act: () => Reaction | Promise<Reaction>;
}

//Performer takes an action,returns a Reaction,(e.g. by running or mocking act())
type Performer = <Reaction>(action:Action<Reaction>) => Promise<Reaction>;


/** MINIMAL ACTION DEFinitioN AND MOCKING SCENARIO */

class ProduceStringAction implements Action<string> {
    constructor(readonly value:string){}
    act() {
        return Promise.resolve(this.value)
    }
}
const expectedAction = new ProduceStringAction("hello");
const mockedReaction = "hello";

/** IDENTITY,TYPE PREDICATE FOR AN ACTION */

export function actionMatches<
  Reaction,Expected extends Action<Reaction>,>(actual: Action<any>,expected: Expected): actual is Expected {
  return (
    actual.constructor === expected.constructor &&
    JSON.stringify(actual) === JSON.stringify(expected)
  );
}

/** EXAMPLE PERFORMERS */

//Act performer is simple - always calls act() to produce a Reaction
const ACT_PERFORM:Performer =  async (action) => await action.act();

//Mock performer tries to intercept a specific action,mock its reaction.
const MOCK_PERFORM:Performer = async (action) => {
    if(actionMatches(action,expectedAction)){
        return mockedReaction
    }
    return await ACT_PERFORM(action)
}

const value = MOCK_PERFORM(new ProduceStringAction("hello"))

可以在this Typescript playground

进行试验

解决方法

我找到了一个解决方案,它编译和运行符合给定操作的反应类型的预期返回类型。

为 MOCK_PERFORM 的 action 参数引入显式 Action 类型足以阻止编译器陷入类型狭窄的兔子洞,从而创建过窄的 Reaction 类型,并阻止模拟的 Reaction 被允许。

证明 mockedResultrealResult 的类型都被正确推断为 string 并且下面的代码可以在有和没有模拟的情况下运行以产生相同的结果。

/** DEFINE ACTION AND PERFORMER */

//Defines act() - a method which produces a Reaction
interface Action<Reaction> {
  act: () => Reaction | Promise<Reaction>;
}

//Performer takes an action,returns a Reaction,(e.g. by running or mocking act())
type Performer = <Reaction>(action:Action<Reaction>) => Promise<Reaction>;


/** MINIMAL ACTION DEFINITION AND MOCKING SCENARIO */

class ProduceStringAction implements Action<string> {
    constructor(readonly value:string){}
    act() {
        return Promise.resolve(this.value)
    }
}
const expectedAction = new ProduceStringAction("hello");
const mockedReaction = "hello";

/** IDENTITY,TYPE PREDICATE FOR AN ACTION */

function actionMatches<
  Reaction,Expected extends Action<Reaction>,>(actual: Action<any>,expected: Expected): actual is Expected {
  return (
    actual.constructor === expected.constructor &&
    JSON.stringify(actual) === JSON.stringify(expected)
  );
}

/** EXAMPLE PERFORMERS */

//Act performer is simple - always calls act() to produce a Reaction
const ACT_PERFORM:Performer =  async (action) => await action.act();

//Mock performer tries to intercept a specific action,mock its reaction.
const MOCK_PERFORM:Performer = async (action:Action<any>) => {
    if(actionMatches(action,expectedAction)){
        return mockedReaction
    }
    return await ACT_PERFORM(action)
}

async function testOut(){
  const mockedResult = await MOCK_PERFORM(new ProduceStringAction("hello"))
  const realResult = await ACT_PERFORM(new ProduceStringAction("hello"));
  console.log(`Real '${realResult}' Mocked '${mockedResult}'`)
}

testOut()

Typescript playground for solution