如何模拟 ES6 超类并用 jest.js 监视它?
Posted
技术标签:
【中文标题】如何模拟 ES6 超类并用 jest.js 监视它?【英文标题】:How to mock ES6 super class and spy on it with jest.js? 【发布时间】:2019-12-18 20:26:29 【问题描述】:我有一个类Qux
继承自类Baa
,我想在测试Qux
时模拟Baa
。如果我不尝试监视模拟 BaaMock
,这原则上是有效的。
如果我想监视模拟类,doc 说我应该使用 jest.fn() 而不是类。但是,这似乎无法正常工作:继承类Qux
的一些方法丢失了。
一些示例代码(也可在https://github.com/stefaneidelloth/testDemoES6Jest 获得):
超级类 Baa (/src/baa.js):
import Foo from './foo.js';
export default class Baa extends Foo
constructor(name)
super(name);
baaMethod()
return 'baaMethod';
overridableMethod()
return 'baa';
继承类Qux(/src/qux.js):
import Baa from './baa.js';
export default class Qux extends Baa
constructor(name)
super(name);
quxMethod()
return 'quxMethod';
overridableMethod()
return 'qux';
A. 测试继承类 Qux 而不能被窥探(/test/qux.test.js):
jest.mock('./../src/baa.js', () =>
return class BaaMock
constructor(name)
this.name = name;
baaMethod()
return 'baaMockedMethod';
);
import Qux from './../src/qux.js';
describe('Qux', function()
var sut;
beforeEach(function()
sut = new Qux('qux');
);
it('quxMethod', function()
expect(sut.quxMethod()).toEqual('quxMethod');
);
it('baaMethod', function()
expect(sut.baaMethod()).toEqual('baaMockedMethod');
);
it('overridableMethod', function()
expect(sut.overridableMethod()).toEqual('qux');
);
);
B.为了能够窥探模拟类,我尝试用模拟函数替换该类(另见https://jestjs.io/docs/en/es6-class-mocks):
import Baa from './../src/baa.js';
jest.mock('./../src/baa.js',
function()
return jest.fn().mockImplementation(
function(name)
return
name:name,
baaMethod: () => return 'baaMockedMethod';
;
);
);
import Qux from './../src/qux.js';
describe('Qux', function()
var sut;
beforeEach(function()
//Baa.mockClear();
sut = new Qux('qux');
//expect(Baa).toHaveBeenCalledTimes(1);
);
it('quxMethod', function()
expect(sut.quxMethod()).toEqual('quxMethod');
);
it('baaMethod', function()
expect(sut.baaMethod()).toEqual('baaMockedMethod');
);
it('overridableMethod', function()
expect(sut.overridableMethod()).toEqual('qux');
);
);
因此,测试失败并出现以下错误:
FAIL test/qux.test.js
Qux
× quxMethod (7ms)
√ baaMethod (4ms)
× overridableMethod (2ms)
● Qux › quxMethod
TypeError: sut.quxMethod is not a function
28 |
29 | it('quxMethod', function()
> 30 | expect(sut.quxMethod()).toEqual('quxMethod');
| ^
31 | );
32 |
33 | it('baaMethod', function()
at Object.quxMethod (test/qux.test.js:30:14)
● Qux › overridableMethod
TypeError: sut.overridableMethod is not a function
36 |
37 | it('overridableMethod', function()
> 38 | expect(sut.overridableMethod()).toEqual('qux');
| ^
39 | );
40 |
41 | );
at Object.overridableMethod (test/qux.test.js:38:14)
我希望我的实例 sut
或 Qux
仍包含由类 Qux 定义的方法 quxMethod
和 overridableMethod
。
=>这是开玩笑的错误吗?
=>如果不是,为什么我需要在模拟中为 Baa 实现 Qux 的所有方法!!???
=>我需要如何调整我的示例代码 B 成功地模拟了 Baa 类,以便 Qux 仍然能够从它继承?
【问题讨论】:
【参考方案1】:我相信你不应该那样做。我认为避免这种方法的理由很少:
-
是的,您必须列出模拟中的所有方法。甚至更多:对于某些方法,您将必须提供实际实现(如果某些方法应该返回布尔值而其他方法依赖于结果怎么办?如果某些方法返回用于其他方法中的某些计算的数字怎么办?)。很难实现,也很难维护,重构时很容易崩溃。
您实际上是在测试实现细节。这永远不会结束。如果有一天你想切换到不同的父类或只是将 2 个类合并为一个 - 你肯定需要更新 test.即使系统仍然可以正常工作。所以你需要做一些没有任何价值的额外工作——只是为了保持测试通过。值得吗?
模拟超类实际上意味着如果系统运行良好,您将不太自信。说对子类的测试模拟了一些超级方法,并且模拟与实际实现不同。你的测试通过了,但真正的系统失败了。
总结以上所有内容,我建议您不惜一切代价避免嘲笑超类。
【讨论】:
好吧,我的问题不是“我应该模拟一些 ES6 超类吗?”。尽管如此,还是感谢您的评论。与您的“答案”相关的问题:***.com/questions/1595166/… @Stefan 当然,我知道这不是您问题的实际答案。我什至期待一些downvoting。但无论如何我想警告:那条路很痛苦,我去过那里。【参考方案2】:在mockImplementation
的函数内部可以使用上下文this
。以下代码有效:
import Baa from './../src/baa.js';
jest.mock('./../src/baa.js',
function()
return jest.fn().mockImplementation(
function(name)
this.name=name;
this.baaMethod = ()=>
return 'baaMockedMethod';
;
return this;
);
);
import Qux from './../src/qux.js';
describe('Qux', function()
var sut;
beforeEach(function()
Baa.mockClear();
sut = new Qux('qux');
expect(Baa).toHaveBeenCalledTimes(1);
);
it('quxMethod', function()
expect(sut.quxMethod()).toEqual('quxMethod');
);
it('baaMethod', function()
expect(sut.baaMethod()).toEqual('baaMockedMethod');
);
it('overridableMethod', function()
expect(sut.overridableMethod()).toEqual('qux');
);
);
【讨论】:
【参考方案3】:修改this
将覆盖原来的方法。对于你的例子,如果我想模拟overridableMethod
,我会修改overridableMethod
的this
:
jest.mock('../../src/baa.js',
function()
return jest.fn().mockImplementation(
function(name)
this.name=name;
this.baaMethod = ()=>
return 'baaMockedMethod';
;
// mock overridable method
this.overridableMethod = ()=>
return 'mock'
return this;
);
);
这会导致测试错误:
● Qux › overridableMethod
expect(received).toEqual(exptected) // deep equality
Expected: "qux"
Received: "mock"
51 |
52 | it("overridableMethod", function ()
> expect(sut.overridableMethod()).toEqual("qux");
| ^
54 | );
55 | );
56 |
at Object.<anonymous> (tests/unit/qux.spec.js:53:37)
你可以使用类模拟来解决这个问题:
const mockConstructor = jest.fn().mockImplementation(function(name)this.name = name)
const mockOverridableMethod = jest.fn(() => 'mock')
const mockBaaMethod = jest.fn(() => 'baaMockedMethod')
jest.mock("../../src/baa.js", () =>
return class BaaMock
constructor(name)
return (mockConstructor.bind(this))(name)
overridableMethod()
return mockOverridableMethod()
baaMethod()
return mockBaaMethod()
;
);
import Qux from "../../src/qux.js";
describe("Qux", function ()
var sut;
beforeEach(function ()
mockConstructor.mockClear()
sut = new Qux("qux");
expect(mockConstructor).toHaveBeenCalledTimes(1); // use mockConstructor check if have been called
);
it("quxMethod", function ()
expect(sut.quxMethod()).toEqual("quxMethod");
);
it("baaMethod", function ()
expect(sut.baaMethod()).toEqual("baaMockedMethod");
);
it("overridableMethod", function ()
expect(sut.overridableMethod()).toEqual("qux");
);
);
对每个函数(包括构造函数)使用jest.fn()
,让我们可以窥探这些函数。
【讨论】:
以上是关于如何模拟 ES6 超类并用 jest.js 监视它?的主要内容,如果未能解决你的问题,请参考以下文章