我一直在学习使用Jest库编写JavaScript / TypeScript代码的单元测试。这是一个我不知道如何处理的示例。它使用TypeScript键入-只有两个公共方法和一个需要service1参数的构造函数。
我认为我需要测试两种情况:
如果this.attr is <= 42
发生递增,
如果this.attr is > 42
和方法end()
触发。
我的问题是:
我无法访问attr
属性,它是私有的,我不知道如何为它分配任何值(也许是在测试实例时,但idk如何)
我不知道this.service1.get()
函数是什么。我没有在代码中看到它的任何实现,也不知道它是如何工作的。我应该将其作为参数传递给此类的实例吗?
在这个特定示例中,我应该使用fakeTimers还是模拟/间谍感到困惑?
export class Class4 {
private attr: number;
private intervalId;
constructor(private service1) { }
public method() {
this.intervalId = setInterval(() => {
if (this.service1.get() > 42) {
this.end()
} else {
this.attr++;
}
}, 100);
}
public getAttr() {
return this.attr;
}
private end() {
clearInterval(this.intervalId);
}
}
仅在我描述的两种情况下,我才需要您在Jest中编写测试的帮助。
编辑。
这是基于此类的简单测试。它没有分配值this.attr
(尽管我的参数值被赋给service1
),并且运行测试后,我收到一条错误消息
Expected: 40
Received: undefined
代码:
it('should stop incrementing Class4.attr if it\'s > 42', () => {
const class4 = new Class4(40);
const attrVal = class4.getAttr();
expect(attrVal).toBe(40);
});
答案 0 :(得分:1)
我不太确定这是否有帮助,但是下面是一个示例,说明如何使用Jest测试类似内容。
这是将您的代码从Typescript转换为es6并附带一个简单的伪Jest实现。 将示例本身留在单独的脚本中。
在此测试中,伪造的Jest仅实现必需的Jest匹配器:expect,toBeGreaterThan,not,toHaveBeenCalledTimes。
以及以下Jest实用程序:useFakeTimers,advanceTimersByTime,clearAllTimers,mock
// self calling function is required to simulate Class4 module and for fake Jest mock to work
(function() {
// translated from typescript to es6
class Class4 {
attr = 0;
intervalId = null;
constructor(service1) {
this.service1 = service1;
}
method() {
this.intervalId = setInterval(() => {
if (this.service1.get() > 42) {
this.end();
} else {
this.attr++;
}
}, 100);
}
getAttr() {
return this.attr;
}
end() {
clearInterval(this.intervalId);
}
}
// this is required to simulate Class4 module and for fake Jest mock to work
window.Class4 = Class4;
})();
// even if we do not know exactly what Service is,
// we know that it has a get method which returns a varying number.
// so this implementation of Service will do
// (it's ok since we're testing Class4, not Service)
class ServiceImpl {
v = 0;
set(v) { this.v = v; }
get() { return this.v; }
}
// after this call, jest will control the flow of
// time in the following tests
// (reimplements the global methods setInterval, setTimeout...etc)
jest.useFakeTimers();
// actually it should be jest.mock('<path to your module>')
// but remember we're using a fake Jest working in SO's snippet)
// now Class4 is a mock
jest.mock(Class4);
// we need a Service instance for a Class4 object to be instanciated
const service = new ServiceImpl();
const class4 = new Class4(service);
it('Class4 constructor has been called 1 time', () => {
expect(Class4).toHaveBeenCalledTimes(1);
});
it('should be incrementing Class4.attr if service.get() < 42', () => {
// service.get() will return 40
service.set(40);
// storing initial attr val
let lastAttrVal = class4.getAttr();
// now class4 is running and should be incrementing
class4.method();
// jest controls the time, advances time by 1 second
jest.advanceTimersByTime(1000);
expect(class4.getAttr()).toBeGreaterThan(lastAttrVal);
});
it('should have been called Class4.end 0 time', () => {
expect(Class4.mock.instances[0].end).toHaveBeenCalledTimes(0);
});
it('should stop incrementing Class4.attr if service.get() > 42', () => {
// service.get() will now return 45, this should end class4
// incrementation in the next interval
service.set(45);
// storing current attr val
let lastAttrVal = class4.getAttr();
jest.advanceTimersByTime(1000);
expect(class4.getAttr()).not.toBeGreaterThan(lastAttrVal);
});
it('end should have been called end 1 time', () => {
expect(Class4.mock.instances[0].end).toHaveBeenCalledTimes(1);
});
jest.clearAllTimers();
<script type="text/javascript">
window.jest = {};
jest.useFakeTimers = () => {
jest.oldSetTimeout = window.setTimeout;
jest.oldSetInterval = window.setInterval;
jest.oldClearTimeout = window.clearTimeout;
jest.oldClearInterval = window.clearInterval;
jest.time = 0;
jest.runningIntervals = [];
window.setInterval = (callback, delay) => {
let interIndex = jest.runningIntervals.findIndex(i => i.cancelled);
let inter = interIndex !== -1 && jest.runningIntervals[interIndex];
if (!inter) {
inter = {};
interIndex = jest.runningIntervals.length;
jest.runningIntervals.push(inter);
}
Object.assign(
inter,
{
start: jest.time,
last: jest.time,
callback,
delay,
cancelled: false
}
);
callback();
return interIndex;
};
window.clearInterval = idx => {
jest.runningIntervals[idx].cancelled = true;
};
jest.advanceTimersByTime = advance => {
for (const end = jest.time + advance;jest.time < end; jest.time++) {
jest.runningIntervals.forEach(inter => {
if (!inter.cancelled && jest.time - inter.last >= inter.delay) {
inter.last = jest.time;
inter.callback();
}
});
}
};
jest.clearAllTimers = () => {
jest.runningIntervals.length = 0;
window.setTimeout = jest.oldSetTimeout;
window.setInterval = jest.oldSetInterval;
window.clearTimeout = jest.oldClearTimeout;
window.clearInterval = jest.oldClearInterval;
};
};
jest.resolve = (v) => {
console.log(v ? 'PASS' : 'FAIL');
}
window.it = (description, test) => {
console.log(description);
test();
};
window.expect = (received) => {
return {
toBeGreaterThan: (expected) => jest.resolve(received > expected),
not: {
toBeGreaterThan: (expected) => jest.resolve(received <= expected),
},
toHaveBeenCalledTimes: (expected) => jest.resolve((received ? received.mock.calls.length : 0) === expected),
}
}
jest.mock = (cls) => {
if (cls.mock) return;
const mock = {
instances: [],
calls: []
}
const proto0 = cls.prototype;
function ClassMock(...args) {
mock.calls.push(args);
this.instance = new proto0.constructor(...args);
this.instanceMock = {};
mock.instances.push(this.instanceMock);
Object.getOwnPropertyNames(proto0).forEach((member) => {
if (member === 'constructor' || typeof proto0[member] !== 'function') return;
this.instanceMock[member] = this.instanceMock[member] || { mock: { calls: [] } };
this.instance[member] = (function(...args) {
this.instanceMock[member].mock.calls.push(args);
return proto0[member].apply(this.instance, [args]);
}).bind(this);
});
}
Object.getOwnPropertyNames(proto0).forEach((member) => {
if (member === 'constructor' || typeof proto0[member] !== 'function') return;
ClassMock.prototype[member] = function(...args) {
return this.instance[member](...args);
}
});
ClassMock.mock = mock;
window[proto0.constructor.name] = ClassMock;
}
</script>