用玩笑模拟打字稿界面

Posted

技术标签:

【中文标题】用玩笑模拟打字稿界面【英文标题】:Mock a typescript interface with jest 【发布时间】:2019-02-06 21:16:41 【问题描述】:

是否可以用 jest 模拟 typescript 界面?

例如:

import  IMultiplier  from "./IMultiplier";

export class Math 
  multiplier: IMultiplier;

  public multiply (a: number, b: number) 
    return this.multiplier.multiply(a, b);
  

然后在测试中:

import  Math  from "../src/Math";
import  IMultiplier  from "../src/IMultiplier";

describe("Math", () => 

    it("can multiply", () => 
        let mathlib = new Math();
        mathlib.multiplier = // <--- assign this property a mock
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
    );
);

我尝试了多种方式来创建一个模拟对象来满足这一点,但都没有奏效。例如给它分配这个模拟:

let multiplierMock = jest.fn(() => ( multiply: jest.fn() ));

会产生以下内容:

Error - Type 'Mock< multiply: Mock<>; >' is not assignable to type 'IMultiplier'.

【问题讨论】:

如何在Math 的实例中创建/分配multiplier @brian-lives-outdoors 这显然是一个人为的例子,但是在代码库中,multiplier 会被传递到 Math 的构造函数中,并且之后会分配给 multiplier 属性的实例(例如以上测试)。 【参考方案1】:

@Brian Adams 的答案不起作用如果 multiplier 属性是 protected 属性。 在这种情况下,我们可以这样做:目标类

import  IMultiplier  from "./IMultiplier";

export class Math 
  protected multiplier: IMultiplier;

  public multiply (a: number, b: number) 
    return this.multiplier.multiply(a, b);
  

单元测试

import  Math  from "../src/Math";
import  IMultiplier  from "../src/IMultiplier";

describe("Math", () => 
    class DummyMultiplier implements IMultiplier 
        public multiply(a, b) 
            // dummy behavior
            return a * b;
        
    

    class TestableMathClass extends Math 
        constructor() 
            super();
            // set the multiplier equal to DummyMultiplier
            this.multiplier = new DummyMultiplier();
        
    

    it("can multiply", () => 
        // here we use the right TestableMathClass
        let mathlib = new TestableMathClass();
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
    );

    it("can multiply and spy something...", () => 
        // with spy we can verify if the function was called
        const spy = jest
            .spyOn(DummyMultiplier.prototype, 'multiply')
            .mockImplementation((_a, _b) => 0);

        let mathlib = new TestableMathClass();
        let result = mathlib.multiply(10, 2);
        expect(result).toEqual(20);
        expect(spy).toBeCalledTimes(1);
    );
);

如果您正在使用 private 属性,也许您可以注入该属性。因此,在单元测试中,您还可以创建一个虚拟行为并注入它。

【讨论】:

【参考方案2】:

试用moq.ts 库。

import Mock from "moq.ts";

const multiplier = new Mock<IMultiplier>()
   .setup(instance => instance.multiply(3, 4))
   .returns(12)
   .object();

let mathlib = new Math();
mathlib.multiplier = multiplier;

【讨论】:

OP 专门询问如何在 Jest 中实现这一点。虽然起订量可能是一个可行的选择,但这不是 OP 所要求的。【参考方案3】:

我创建了一个库,允许您模拟 TypeScript 接口 - https://github.com/marchaos/jest-mock-extended。

似乎没有库可以干净地做到这一点,同时保持完全的类型安全。它大致基于这里的讨论 -https://github.com/facebook/jest/issues/7832

【讨论】:

嗨@mar​​chaos,这个库看起来真的很棒,但它似乎已经好几个月没有维护了。有没有让它复活的计划? 现在应该维护 :)【参考方案4】:

模拟只需要与界面具有相同的形状

(来自docs:TypeScript 的核心原则之一是类型检查关注值的形状。这有时被称为“鸭子类型”或“结构子类型”。

所以mathlib.multiplier只需分配给符合IMultiplier的对象即可。

我猜示例中的 IMultiplier 看起来像这样:

interface IMultiplier 
  multiply(a: number, b: number): number

因此,通过将有问题的行更改为以下内容,示例测试将正常工作:

mathlib.multiplier = 
  multiply: jest.fn((a, b) => a * b)
;

【讨论】:

尽管从技术上讲,mock 只需要与界面具有相同的形状,但这没有抓住重点。关键是要有一种方便的方法来生成给定接口的模拟,这样开发人员就不必手动创建模拟类,例如,每次需要运行时从十几个方法中提取一个函数测试。 更不用说根据您项目的 ESLint 设置,此解决方案可能无法正常工作。

以上是关于用玩笑模拟打字稿界面的主要内容,如果未能解决你的问题,请参考以下文章

用玩笑测试打字稿中的私有函数

尝试使用 create-react-app 开玩笑地运行打字稿

运行玩笑时打字稿路径无法解析?

带有打字稿的玩笑无法识别功能完成()

markdown 开玩笑,打字稿

开玩笑不使用 fs/promises 打字稿