用玩笑模拟打字稿界面
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
【讨论】:
嗨@marchaos,这个库看起来真的很棒,但它似乎已经好几个月没有维护了。有没有让它复活的计划? 现在应该维护 :)【参考方案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 设置,此解决方案可能无法正常工作。以上是关于用玩笑模拟打字稿界面的主要内容,如果未能解决你的问题,请参考以下文章