Skip to content

Commit e484301

Browse files
committed
foobar
1 parent adaf602 commit e484301

2 files changed

Lines changed: 173 additions & 0 deletions

File tree

lib/internal/test_runner/mock.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
'use strict';
2+
3+
function noop() {}
4+
5+
class MockFunctionContext {
6+
#_calls;
7+
#_mocks;
8+
#_implementation;
9+
#_restore;
10+
#_tracker;
11+
12+
constructor(tracker, implementation, restore) {
13+
this.#_calls = [];
14+
this.#_mocks = new Map();
15+
this.#_implementation = implementation;
16+
this.#_restore = restore;
17+
this.#_tracker = tracker;
18+
}
19+
20+
get calls() {
21+
return this.#_calls.slice(0);
22+
}
23+
24+
mockImplementation(fn) {
25+
this.#_implementation = fn;
26+
}
27+
28+
mockImplementationOnce(fn, onCall) {
29+
const nextCall = this.#_calls.length;
30+
const call = onCall ?? nextCall;
31+
32+
if (call < nextCall) {
33+
// The call number has already passed.
34+
return;
35+
}
36+
37+
this.#_mocks.set(call, fn);
38+
}
39+
40+
nextMock() {
41+
const nextCall = this.#_calls.length;
42+
const mock = this.#_mocks.get(nextCall);
43+
44+
this.#_mocks.delete(nextCall);
45+
return mock;
46+
}
47+
48+
get implementation() {
49+
return this.#_implementation;
50+
}
51+
52+
trackCall(call) {
53+
this.#_calls.push(call);
54+
}
55+
56+
restore() {
57+
if (this.#_restore === null) {
58+
// This is a bare function mock.
59+
return;
60+
}
61+
62+
// This is an object method mock.
63+
const { descriptor, object, methodName } = this.#_restore;
64+
65+
Object.defineProperty(object, methodName, descriptor);
66+
}
67+
}
68+
69+
const { nextMock, trackCall } = MockFunctionContext.prototype;
70+
delete MockFunctionContext.prototype.trackCall;
71+
delete MockFunctionContext.prototype.nextMock;
72+
73+
class MockTracker {
74+
constructor() {
75+
76+
}
77+
78+
fn(implementation) {
79+
if (implementation === undefined) {
80+
implementation = noop;
81+
}
82+
83+
// TODO(cjihrig): Assert types
84+
85+
const ctx = new MockFunctionContext(this, implementation, null);
86+
87+
return this.#setupMock(ctx, implementation);
88+
}
89+
90+
method(object, methodName, implementation, options) {
91+
// TODO(cjihrig): Assert inputs.
92+
// TODO(cjihrig): Assert this is really a method.
93+
// TODO(cjihrig): Throw if this is already a mock.
94+
95+
const descriptor = Object.getOwnPropertyDescriptor(object, methodName);
96+
let original;
97+
98+
if (options?.getter) {
99+
original = descriptor.get;
100+
} else if (options?.setter) {
101+
original = descriptor.set;
102+
} else {
103+
original = descriptor.value;
104+
}
105+
106+
const restore = { descriptor, object, methodName };
107+
const ctx = new MockFunctionContext(this, implementation, restore);
108+
const mock = this.#setupMock(ctx, original);
109+
const mockDescriptor = {
110+
configurable: descriptor.configurable,
111+
enumerable: descriptor.enumerable,
112+
};
113+
114+
if (options?.getter) {
115+
mockDescriptor.get = mock;
116+
mockDescriptor.set = descriptor.set;
117+
} else if (options?.setter) {
118+
mockDescriptor.get = descriptor.get;
119+
mockDescriptor.set = mock;
120+
} else {
121+
mockDescriptor.writable = descriptor.writable;
122+
mockDescriptor.value = mock;
123+
}
124+
125+
Object.defineProperty(object, methodName, mockDescriptor);
126+
127+
return mock;
128+
}
129+
130+
#setupMock(ctx, fnToMatch) {
131+
const mock = function(...args) {
132+
const onceMock = nextMock.call(ctx);
133+
const fn = onceMock ?? ctx.implementation;
134+
const result = new.target ? new fn(...args) : fn.call(this, ...args);
135+
136+
trackCall.call(ctx, {
137+
arguments: args,
138+
result,
139+
stack: new Error(),
140+
target: new.target,
141+
this: this,
142+
});
143+
144+
return result;
145+
};
146+
147+
Object.defineProperty(mock, 'mock', {
148+
get() {
149+
return ctx;
150+
}
151+
});
152+
153+
Object.defineProperty(mock, 'name', {
154+
value: fnToMatch.name,
155+
writable: false,
156+
enumerable: false,
157+
configurable: true,
158+
});
159+
160+
Object.defineProperty(mock, 'length', {
161+
value: fnToMatch.length,
162+
writable: false,
163+
enumerable: false,
164+
configurable: true,
165+
});
166+
167+
return mock;
168+
}
169+
}
170+
171+
module.exports = { MockTracker };

lib/internal/test_runner/test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
kIsNodeError,
1616
} = require('internal/errors');
1717
const { getOptionValue } = require('internal/options');
18+
const { MockTracker } = require('internal/test_runner/mock');
1819
const { TapStream } = require('internal/test_runner/tap_stream');
1920
const { createDeferredPromise } = require('internal/util');
2021
const { isPromise } = require('internal/util/types');
@@ -39,6 +40,7 @@ class TestContext {
3940

4041
constructor(test) {
4142
this.#test = test;
43+
this.mock = new MockTracker();
4244
}
4345

4446
diagnostic(message) {

0 commit comments

Comments
 (0)