Test libraries replace functions with spies that record how they were used. Write createSpy(impl). It returns a function, the spy, that records each call and then calls impl, if one was given, with the same this and arguments, returning what impl returns. Without impl, the spy returns undefined.
spy.calls is an array with one entry per call, in order: { args, this }, where args is an array of the arguments.
spy.results has one entry per call, in the same order: { type: "return", value } with the returned value, or { type: "throw", value } with the error when impl throws. A call that throws is still recorded, and the error is thrown on to the caller.
spy.callCount is the number of calls so far. spy.calledWith(...args) returns true if some call had exactly these arguments: the same number of them, each compared with Object.is. spy.reset() forgets every call and result.
const add = createSpy((a, b) => a + b);
add(1, 2); // 3
add(5, 5); // 10
add.callCount; // 2
add.calls[0].args; // [1, 2]
add.results[1]; // { type: "return", value: 10 }
add.calledWith(5, 5); // true
add.calledWith(5); // false
add.reset();
add.callCount; // 0
const fake = createSpy();
fake(1, 2);
fake("a");
fake();
expect(fake.calls.map((call) => call.args)).toEqual([[1, 2], ["a"], []]);
const fake = createSpy();
const obj = { method: fake };
const other = {};
obj.method();
fake.call(other);
expect(fake.calls[0].this).toBe(obj);
expect(fake.calls[1].this).toBe(other);
const add = createSpy(function (a, b) {
return this.base + a + b;
});
const obj = { base: 10, add };
expect(obj.add(1, 2)).toBe(13);
const double = createSpy((x) => x * 2);
double(1);
double(5);
expect(double.results).toEqual([
{ type: "return", value: 2 },
{ type: "return", value: 10 },
]);
const error = new Error("boom");
const broken = createSpy(() => {
throw error;
});
expect(() => broken(1)).toThrow("boom");
expect(broken.calls.map((call) => call.args)).toEqual([[1]]);
expect(broken.results).toEqual([{ type: "throw", value: error }]);
const fake = createSpy();
expect(fake.callCount).toBe(0);
fake();
fake();
expect(fake.callCount).toBe(2);
const fake = createSpy();
const obj = {};
fake(1, "a");
fake(obj);
fake(NaN);
expect(fake.calledWith(1, "a")).toBe(true);
expect(fake.calledWith(obj)).toBe(true);
expect(fake.calledWith(NaN)).toBe(true);
expect(fake.calledWith({})).toBe(false);
expect(fake.calledWith("1", "a")).toBe(false);
const fake = createSpy();
fake(1, 2);
expect(fake.calledWith(1)).toBe(false);
expect(fake.calledWith(1, 2, undefined)).toBe(false);
expect(fake.calledWith()).toBe(false);
const fake = createSpy((x) => x);
fake(1);
fake(2);
fake.reset();
expect(fake.callCount).toBe(0);
expect(fake.calls).toEqual([]);
expect(fake.results).toEqual([]);
expect(fake.calledWith(1)).toBe(false);
fake(3);
expect(fake.calls.map((call) => call.args)).toEqual([[3]]);
expect(fake.results).toEqual([{ type: "return", value: 3 }]);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Build a spy. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.