Skip to content
File

Blob: src/workerd/api/node/tests/assert-test.js

javascript1523 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import {
27 deepEqual,
28 deepStrictEqual,
29 doesNotMatch,
30 doesNotReject,
31 doesNotThrow,
32 equal,
33 fail,
34 ifError,
35 match,
36 notDeepEqual,
37 notDeepStrictEqual,
38 notEqual,
39 notStrictEqual,
40 ok,
41 partialDeepStrictEqual,
42 rejects,
43 strictEqual,
44 throws,
45 AssertionError,
46} from 'node:assert';
47 
48import { mock } from 'node:test';
49import util from 'node:util';
50 
51import { default as assert } from 'node:assert';
52 
53const _strictEqualMessageStart = 'Expected values to be strictly equal:\n';
54const start = 'Expected values to be strictly deep-equal:';
55const actExp = '+ actual - expected';
56 
57export const test_ok = {
58 test(ctrl, env, ctx) {
59 // truthy values just work
60 [true, 1, [], {}, 'hello'].forEach(ok);
61 [true, 1, [], {}, 'hello'].forEach(assert);
62 [true, 1, [], {}, 'hello'].forEach((val) => {
63 doesNotThrow(() => ok(val));
64 });
65 
66 // falsy values throw
67 [false, 0, '', undefined, NaN, null].forEach((val) => {
68 throws(() => ok(val), { name: 'AssertionError' });
69 });
70 
71 // falsy values throw
72 [false, 0, '', undefined, NaN, null].forEach((val) => {
73 throws(() => ok(val, 'message'), { message: 'message' });
74 });
75 },
76};
77 
78export const test_equal = {
79 test(ctrl, env, ctx) {
80 [
81 { a: 1, b: 1 },
82 { a: 1, b: '1', fails: true },
83 { a: 1, b: '1', fails: true, message: 'boom' },
84 { a: 'a', b: 'a' },
85 { a: ctx, b: ctx },
86 ].forEach(({ a, b, fails, message }) => {
87 if (!fails) {
88 equal(a, b);
89 strictEqual(a, b);
90 if (message) {
91 throws(() => notEqual(a, b, message), { message });
92 throws(() => notStrictEqual(a, b, message), { message });
93 } else {
94 throws(() => notEqual(a, b), { name: 'AssertionError' });
95 throws(() => notStrictEqual(a, b), { name: 'AssertionError' });
96 }
97 } else {
98 notEqual(a, b);
99 notStrictEqual(a, b);
100 if (message) {
101 throws(() => equal(a, b, message), { message });
102 throws(() => strictEqual(a, b, message), { message });
103 } else {
104 throws(() => equal(a, b), { name: 'AssertionError' });
105 throws(() => strictEqual(a, b), { name: 'AssertionError' });
106 }
107 }
108 });
109 },
110};
111 
112export const test_fail = {
113 test(ctrl, env, ctx) {
114 throws(() => fail('boom'), { message: 'boom' });
115 throws(() => ifError('boom'));
116 throws(() => ifError(false));
117 doesNotThrow(() => ifError(null));
118 doesNotThrow(() => ifError(undefined));
119 },
120};
121 
122export const test_rejects = {
123 async test(ctrl, env, ctx) {
124 await rejects(Promise.reject(new Error('boom')), { message: 'boom' });
125 await doesNotReject(Promise.resolve(1));
126 },
127};
128 
129export const test_matching = {
130 test(ctrl, env, ctx) {
131 match('hello', /hello/);
132 throws(() => match('hello', /not/), { name: 'AssertionError' });
133 doesNotMatch('hello', /not/);
134 throws(() => doesNotMatch('hello', /hello/), { name: 'AssertionError' });
135 },
136};
137 
138export const test_deep_equal = {
139 test(ctrl, env, ctx) {
140 const a = {
141 b: [
142 {
143 c: new Uint8Array([1, 2, 3]),
144 d: false,
145 e: 'hello',
146 },
147 ],
148 };
149 const b = {
150 b: [
151 {
152 c: new Uint8Array([1, 2, 3]),
153 d: false,
154 e: 'hello',
155 },
156 ],
157 };
158 deepEqual(a, b);
159 deepStrictEqual(a, b);
160 b.b[0].c[0] = 4;
161 notDeepEqual(a, b);
162 notDeepStrictEqual(a, b);
163 },
164};
165 
166export const test_deep_equal_errors = {
167 test(ctrl, env, ctx) {
168 const a = {
169 b: [
170 {
171 c: new Uint8Array([1, 2, 3]),
172 d: false,
173 e: 'hello',
174 },
175 ],
176 };
177 const b = {
178 b: [
179 {
180 c: new Uint8Array([1, 2, 4]),
181 d: true,
182 e: 'hello',
183 },
184 ],
185 };
186 
187 {
188 const expectedError = [
189 `Expected values to be strictly deep-equal:`,
190 `+ actual - expected`,
191 `+ { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: false, e: 'hello' } ] }`,
192 `- { b: [ { c: Uint8Array(3) [ 1, 2, 4 ], d: true, e: 'hello' } ] }`,
193 ];
194 throws(
195 () => deepEqual(a, b),
196 (err) => {
197 assert(err instanceof AssertionError);
198 deepEqual(trimMessage(err.message), expectedError);
199 return true;
200 }
201 );
202 throws(
203 () => deepStrictEqual(a, b),
204 (err) => {
205 assert(err instanceof AssertionError);
206 deepEqual(trimMessage(err.message), expectedError);
207 return true;
208 }
209 );
210 }
211 
212 // Fix one value, message updates
213 b.b[0].c[2] = 3;
214 
215 {
216 const expectedError = [
217 `Expected values to be strictly deep-equal:`,
218 `+ actual - expected`,
219 `+ { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: false, e: 'hello' } ] }`,
220 `- { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: true, e: 'hello' } ] }`,
221 ];
222 throws(
223 () => deepEqual(a, b),
224 (err) => {
225 assert(err instanceof AssertionError);
226 deepEqual(trimMessage(err.message), expectedError);
227 return true;
228 }
229 );
230 throws(
231 () => deepStrictEqual(a, b),
232 (err) => {
233 assert(err instanceof AssertionError);
234 deepEqual(trimMessage(err.message), expectedError);
235 return true;
236 }
237 );
238 }
239 },
240};
241 
242export const test_check_proxies = {
243 test() {
244 const arrProxy = new Proxy([1, 2], {});
245 deepStrictEqual(arrProxy, [1, 2]);
246 const defaultMsgStartFull = `${start}\n${actExp}`;
247 const tmp = util.inspect.defaultOptions;
248 util.inspect.defaultOptions = { showProxy: true };
249 throws(() => deepStrictEqual(arrProxy, [1, 2, 3]), {
250 message:
251 `${defaultMsgStartFull}\n\n` + '+ Proxy([ 1, 2 ])\n' + '- [ 1, 2, 3 ]',
252 });
253 util.inspect.defaultOptions = tmp;
254 
255 const invalidTrap = new Proxy([1, 2, 3], {
256 ownKeys() {
257 return [];
258 },
259 });
260 throws(() => deepStrictEqual(invalidTrap, [1, 2, 3]), {
261 name: 'TypeError',
262 message: "'ownKeys' on proxy: trap result did not include 'length'",
263 });
264 },
265};
266 
267export const test_partial_deep_strict_equal = {
268 test(ctrl, env, ctx) {
269 // Test basic object partial equality
270 const actual = { a: 1, b: 2, c: 3 };
271 const expected = { a: 1, b: 2 };
272 partialDeepStrictEqual(actual, expected);
273 
274 // Test nested object partial equality
275 const actualNested = {
276 x: { y: { z: 1 } },
277 a: 'hello',
278 extra: 'should be ignored',
279 };
280 const expectedNested = { x: { y: { z: 1 } } };
281 partialDeepStrictEqual(actualNested, expectedNested);
282 
283 // Test array partial equality
284 const actualArray = [1, 2, 3, 4, 5];
285 const expectedArray = [1, 2, 3];
286 partialDeepStrictEqual(actualArray, expectedArray);
287 
288 // Test Set partial equality
289 const actualSet = new Set([1, 2, 3, 4]);
290 const expectedSet = new Set([1, 2]);
291 partialDeepStrictEqual(actualSet, expectedSet);
292 
293 // Test Map partial equality
294 const actualMap = new Map([
295 ['a', 1],
296 ['b', 2],
297 ['c', 3],
298 ]);
299 const expectedMap = new Map([
300 ['a', 1],
301 ['b', 2],
302 ]);
303 partialDeepStrictEqual(actualMap, expectedMap);
304 
305 // Test Date objects
306 const date1 = new Date('2023-01-01');
307 const date2 = new Date('2023-01-01');
308 partialDeepStrictEqual(date1, date2);
309 
310 // Test RegExp objects
311 const regex1 = /test/gi;
312 const regex2 = /test/gi;
313 partialDeepStrictEqual(regex1, regex2);
314 
315 // Test Error objects
316 const error1 = new Error('Test error');
317 const error2 = new Error('Test error');
318 partialDeepStrictEqual(error1, error2);
319 
320 // Test circular references - simple self-reference
321 const circularObj1 = { a: 1, b: 2 };
322 circularObj1.self = circularObj1;
323 const circularObj2 = { a: 1 };
324 circularObj2.self = circularObj2;
325 partialDeepStrictEqual(circularObj1, circularObj2);
326 
327 // Test circular references - mutual references
328 const objA = { name: 'A', value: 1 };
329 const objB = { name: 'B', ref: objA };
330 objA.ref = objB;
331 const objC = { name: 'A' };
332 const objD = { name: 'B', ref: objC };
333 objC.ref = objD;
334 partialDeepStrictEqual(objA, objC);
335 
336 // Test mixed circular/non-circular (should still work)
337 const mixedCircular = { a: 1, b: { c: 2 } };
338 mixedCircular.self = mixedCircular;
339 const mixedExpected = { a: 1, b: { c: 2 } };
340 partialDeepStrictEqual(mixedCircular, mixedExpected);
341 
342 // Test primitive equality
343 partialDeepStrictEqual(42, 42);
344 partialDeepStrictEqual('hello', 'hello');
345 partialDeepStrictEqual(null, null);
346 partialDeepStrictEqual(undefined, undefined);
347 
348 // Test failure cases
349 throws(() => partialDeepStrictEqual({ a: 1 }, { a: 2 }), AssertionError);
350 throws(
351 () => partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 }),
352 AssertionError
353 );
354 throws(() => partialDeepStrictEqual([1, 2], [1, 2, 3]), AssertionError);
355 throws(
356 () => partialDeepStrictEqual(new Set([1, 2]), new Set([1, 2, 3])),
357 AssertionError
358 );
359 throws(
360 () =>
361 partialDeepStrictEqual(new Date('2023-01-01'), new Date('2023-01-02')),
362 AssertionError
363 );
364 
365 // Test complex nested case
366 const complexActual = {
367 users: [
368 { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' },
369 { id: 2, name: 'Bob', age: 25, email: 'bob@example.com' },
370 ],
371 config: {
372 theme: 'dark',
373 notifications: true,
374 advanced: { debug: false, verbose: true },
375 },
376 metadata: { version: '1.0.0', timestamp: Date.now() },
377 };
378 
379 const complexExpected = {
380 users: [
381 { id: 1, name: 'Alice' },
382 { id: 2, name: 'Bob' },
383 ],
384 config: {
385 theme: 'dark',
386 advanced: { debug: false },
387 },
388 };
389 
390 partialDeepStrictEqual(complexActual, complexExpected);
391 },
392};
393 
394function trimMessage(msg) {
395 return msg
396 .trim()
397 .split('\n')
398 .map((line) => line.trim())
399 .filter(Boolean);
400}
401 
402export const test_mocks = {
403 test() {
404 const fn = mock.fn(() => {});
405 fn(1, 2, 3);
406 strictEqual(fn.mock.callCount(), 1);
407 strictEqual(fn.mock.calls[0].arguments.length, 3);
408 deepStrictEqual(fn.mock.calls[0].arguments, [1, 2, 3]);
409 
410 fn.mock.mockImplementation(() => 42);
411 strictEqual(fn(), 42);
412 strictEqual(fn.mock.callCount(), 2);
413 
414 fn.mock.resetCalls();
415 strictEqual(fn.mock.callCount(), 0);
416 },
417};
418 
419export const spiesOnFunction = {
420 test() {
421 const sum = mock.fn((arg1, arg2) => {
422 return arg1 + arg2;
423 });
424 
425 strictEqual(sum.mock.calls.length, 0);
426 strictEqual(sum(3, 4), 7);
427 strictEqual(sum.call(1000, 9, 1), 10);
428 strictEqual(sum.mock.calls.length, 2);
429 
430 let call = sum.mock.calls[0];
431 deepStrictEqual(call.arguments, [3, 4]);
432 strictEqual(call.error, undefined);
433 strictEqual(call.result, 7);
434 strictEqual(call.target, undefined);
435 strictEqual(call.this, undefined);
436 
437 call = sum.mock.calls[1];
438 deepStrictEqual(call.arguments, [9, 1]);
439 strictEqual(call.error, undefined);
440 strictEqual(call.result, 10);
441 strictEqual(call.target, undefined);
442 strictEqual(call.this, 1000);
443 },
444};
445 
446export const speiesOnBoundFunction = {
447 test() {
448 const bound = function (arg1, arg2) {
449 return this + arg1 + arg2;
450 }.bind(50);
451 const sum = mock.fn(bound);
452 
453 strictEqual(sum.mock.calls.length, 0);
454 strictEqual(sum(3, 4), 57);
455 strictEqual(sum(9, 1), 60);
456 strictEqual(sum.mock.calls.length, 2);
457 
458 let call = sum.mock.calls[0];
459 deepStrictEqual(call.arguments, [3, 4]);
460 strictEqual(call.result, 57);
461 strictEqual(call.target, undefined);
462 strictEqual(call.this, undefined);
463 
464 call = sum.mock.calls[1];
465 deepStrictEqual(call.arguments, [9, 1]);
466 strictEqual(call.result, 60);
467 strictEqual(call.target, undefined);
468 strictEqual(call.this, undefined);
469 },
470};
471 
472export const spiesOnConstructor = {
473 test() {
474 class ParentClazz {
475 constructor(c) {
476 this.c = c;
477 }
478 }
479 
480 class Clazz extends ParentClazz {
481 #privateValue;
482 
483 constructor(a, b) {
484 super(a + b);
485 this.a = a;
486 this.#privateValue = b;
487 }
488 
489 getPrivateValue() {
490 return this.#privateValue;
491 }
492 }
493 
494 const ctor = mock.fn(Clazz);
495 const instance = new ctor(42, 85);
496 
497 ok(instance instanceof Clazz);
498 ok(instance instanceof ParentClazz);
499 strictEqual(instance.a, 42);
500 strictEqual(instance.getPrivateValue(), 85);
501 strictEqual(instance.c, 127);
502 strictEqual(ctor.mock.calls.length, 1);
503 
504 const call = ctor.mock.calls[0];
505 
506 deepStrictEqual(call.arguments, [42, 85]);
507 strictEqual(call.error, undefined);
508 strictEqual(call.result, instance);
509 strictEqual(call.target, Clazz);
510 strictEqual(call.this, instance);
511 },
512};
513 
514export const noopSpyCreatedByDefault = {
515 test() {
516 const fn = mock.fn();
517 
518 strictEqual(fn.mock.calls.length, 0);
519 strictEqual(fn(3, 4), undefined);
520 strictEqual(fn.mock.calls.length, 1);
521 
522 const call = fn.mock.calls[0];
523 deepStrictEqual(call.arguments, [3, 4]);
524 strictEqual(call.result, undefined);
525 strictEqual(call.target, undefined);
526 strictEqual(call.this, undefined);
527 },
528};
529 
530export const internalNoOpFunctionCanBeReused = {
531 test() {
532 const fn1 = mock.fn();
533 fn1.prop = true;
534 const fn2 = mock.fn();
535 
536 fn1(1);
537 fn2(2);
538 fn1(3);
539 
540 notStrictEqual(fn1.mock, fn2.mock);
541 strictEqual(fn1.mock.calls.length, 2);
542 strictEqual(fn2.mock.calls.length, 1);
543 strictEqual(fn1.prop, true);
544 strictEqual(fn2.prop, undefined);
545 },
546};
547 
548export const functionsCanBeMockedMultipleTimesAtOnce = {
549 test() {
550 function sum(a, b) {
551 return a + b;
552 }
553 
554 function difference(a, b) {
555 return a - b;
556 }
557 
558 function product(a, b) {
559 return a * b;
560 }
561 
562 const fn1 = mock.fn(sum, difference);
563 const fn2 = mock.fn(sum, product);
564 
565 strictEqual(fn1(5, 3), 2);
566 strictEqual(fn2(5, 3), 15);
567 strictEqual(fn2(4, 2), 8);
568 ok(!('mock' in sum));
569 ok(!('mock' in difference));
570 ok(!('mock' in product));
571 notStrictEqual(fn1.mock, fn2.mock);
572 strictEqual(fn1.mock.calls.length, 1);
573 strictEqual(fn2.mock.calls.length, 2);
574 },
575};
576 
577export const internalNoopFunctionCanBeReusedAsMethods = {
578 test() {
579 const obj = {
580 _foo: 5,
581 _bar: 9,
582 foo() {
583 return this._foo;
584 },
585 bar() {
586 return this._bar;
587 },
588 };
589 
590 mock.method(obj, 'foo');
591 obj.foo.prop = true;
592 mock.method(obj, 'bar');
593 strictEqual(obj.foo(), 5);
594 strictEqual(obj.bar(), 9);
595 strictEqual(obj.bar(), 9);
596 notStrictEqual(obj.foo.mock, obj.bar.mock);
597 strictEqual(obj.foo.mock.calls.length, 1);
598 strictEqual(obj.bar.mock.calls.length, 2);
599 strictEqual(obj.foo.prop, true);
600 strictEqual(obj.bar.prop, undefined);
601 },
602};
603 
604export const methodsCanBeMockedMultipleTimesButNotAtTheSameTime = {
605 test() {
606 const obj = {
607 offset: 3,
608 sum(a, b) {
609 return this.offset + a + b;
610 },
611 };
612 
613 function difference(a, b) {
614 return this.offset + (a - b);
615 }
616 
617 function product(a, b) {
618 return this.offset + a * b;
619 }
620 
621 const originalSum = obj.sum;
622 const fn1 = mock.method(obj, 'sum', difference);
623 
624 strictEqual(obj.sum(5, 3), 5);
625 strictEqual(obj.sum(5, 1), 7);
626 strictEqual(obj.sum, fn1);
627 notStrictEqual(fn1.mock, undefined);
628 strictEqual(originalSum.mock, undefined);
629 strictEqual(difference.mock, undefined);
630 strictEqual(product.mock, undefined);
631 strictEqual(fn1.mock.calls.length, 2);
632 
633 const fn2 = mock.method(obj, 'sum', product);
634 
635 strictEqual(obj.sum(5, 3), 18);
636 strictEqual(obj.sum, fn2);
637 notStrictEqual(fn1, fn2);
638 strictEqual(fn2.mock.calls.length, 1);
639 
640 obj.sum.mock.restore();
641 strictEqual(obj.sum, fn1);
642 obj.sum.mock.restore();
643 strictEqual(obj.sum, originalSum);
644 strictEqual(obj.sum.mock, undefined);
645 },
646};
647 
648export const spiesOnObjectMethod = {
649 test() {
650 const obj = {
651 prop: 5,
652 method(a, b) {
653 return a + b + this.prop;
654 },
655 };
656 
657 strictEqual(obj.method(1, 3), 9);
658 mock.method(obj, 'method');
659 strictEqual(obj.method.mock.calls.length, 0);
660 strictEqual(obj.method(1, 3), 9);
661 
662 const call = obj.method.mock.calls[0];
663 
664 deepStrictEqual(call.arguments, [1, 3]);
665 strictEqual(call.result, 9);
666 strictEqual(call.target, undefined);
667 strictEqual(call.this, obj);
668 
669 strictEqual(obj.method.mock.restore(), undefined);
670 strictEqual(obj.method(1, 3), 9);
671 strictEqual(obj.method.mock, undefined);
672 },
673};
674 
675export const spiesOnGetter = {
676 test() {
677 const obj = {
678 prop: 5,
679 get method() {
680 return this.prop;
681 },
682 };
683 
684 strictEqual(obj.method, 5);
685 
686 const getter = mock.method(obj, 'method', { getter: true });
687 
688 strictEqual(getter.mock.calls.length, 0);
689 strictEqual(obj.method, 5);
690 
691 const call = getter.mock.calls[0];
692 
693 deepStrictEqual(call.arguments, []);
694 strictEqual(call.result, 5);
695 strictEqual(call.target, undefined);
696 strictEqual(call.this, obj);
697 
698 strictEqual(getter.mock.restore(), undefined);
699 strictEqual(obj.method, 5);
700 },
701};
702 
703export const spiesOnSetter = {
704 test() {
705 const obj = {
706 prop: 100,
707 set method(val) {
708 this.prop = val;
709 },
710 };
711 
712 strictEqual(obj.prop, 100);
713 obj.method = 88;
714 strictEqual(obj.prop, 88);
715 
716 const setter = mock.method(obj, 'method', { setter: true });
717 
718 strictEqual(setter.mock.calls.length, 0);
719 obj.method = 77;
720 strictEqual(obj.prop, 77);
721 strictEqual(setter.mock.calls.length, 1);
722 
723 const call = setter.mock.calls[0];
724 
725 deepStrictEqual(call.arguments, [77]);
726 strictEqual(call.result, undefined);
727 strictEqual(call.target, undefined);
728 strictEqual(call.this, obj);
729 
730 strictEqual(setter.mock.restore(), undefined);
731 strictEqual(obj.prop, 77);
732 obj.method = 65;
733 strictEqual(obj.prop, 65);
734 },
735};
736 
737export const spyFunctionsCanBeBound = {
738 test() {
739 const sum = mock.fn(function (arg1, arg2) {
740 return this + arg1 + arg2;
741 });
742 const bound = sum.bind(1000);
743 
744 strictEqual(bound(9, 1), 1010);
745 strictEqual(sum.mock.calls.length, 1);
746 
747 const call = sum.mock.calls[0];
748 deepStrictEqual(call.arguments, [9, 1]);
749 strictEqual(call.result, 1010);
750 strictEqual(call.target, undefined);
751 strictEqual(call.this, 1000);
752 
753 strictEqual(sum.mock.restore(), undefined);
754 strictEqual(sum.bind(0)(2, 11), 13);
755 },
756};
757 
758export const mocksPrototypeMethodsOnAnInstance = {
759 async test() {
760 class Runner {
761 async someTask(msg) {
762 return Promise.resolve(msg);
763 }
764 
765 async method(msg) {
766 await this.someTask(msg);
767 return msg;
768 }
769 }
770 const msg = 'ok';
771 const obj = new Runner();
772 strictEqual(await obj.method(msg), msg);
773 
774 mock.method(obj, obj.someTask.name);
775 strictEqual(obj.someTask.mock.calls.length, 0);
776 
777 strictEqual(await obj.method(msg), msg);
778 
779 const call = obj.someTask.mock.calls[0];
780 
781 deepStrictEqual(call.arguments, [msg]);
782 strictEqual(await call.result, msg);
783 strictEqual(call.target, undefined);
784 strictEqual(call.this, obj);
785 
786 const obj2 = new Runner();
787 // Ensure that a brand new instance is not mocked
788 strictEqual(obj2.someTask.mock, undefined);
789 
790 strictEqual(obj.someTask.mock.restore(), undefined);
791 strictEqual(await obj.method(msg), msg);
792 strictEqual(obj.someTask.mock, undefined);
793 strictEqual(Runner.prototype.someTask.mock, undefined);
794 },
795};
796 
797export const spiesOnAsyncStaticClassMethods = {
798 async test() {
799 class Runner {
800 static async someTask(msg) {
801 return Promise.resolve(msg);
802 }
803 
804 static async method(msg) {
805 await this.someTask(msg);
806 return msg;
807 }
808 }
809 const msg = 'ok';
810 strictEqual(await Runner.method(msg), msg);
811 
812 mock.method(Runner, Runner.someTask.name);
813 strictEqual(Runner.someTask.mock.calls.length, 0);
814 
815 strictEqual(await Runner.method(msg), msg);
816 
817 const call = Runner.someTask.mock.calls[0];
818 
819 deepStrictEqual(call.arguments, [msg]);
820 strictEqual(await call.result, msg);
821 strictEqual(call.target, undefined);
822 strictEqual(call.this, Runner);
823 
824 strictEqual(Runner.someTask.mock.restore(), undefined);
825 strictEqual(await Runner.method(msg), msg);
826 strictEqual(Runner.someTask.mock, undefined);
827 strictEqual(Runner.prototype.someTask, undefined);
828 },
829};
830 
831export const givenNullToAMockMethodItThrowsAInvalidArgumentError = {
832 test() {
833 throws(() => mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });
834 },
835};
836 
837export const itShouldThrowGivenAnInexistentPropertyOnAObjectInstance = {
838 test() {
839 throws(() => mock.method({ abc: 0 }, 'non-existent'), {
840 code: 'ERR_INVALID_ARG_VALUE',
841 });
842 },
843};
844 
845export const spyFunctionsCanBeUsedOnClassesInheritance = {
846 test() {
847 // Makes sure that having a null-prototype doesn't throw our system off
848 class A extends null {
849 static someTask(msg) {
850 return msg;
851 }
852 static method(msg) {
853 return this.someTask(msg);
854 }
855 }
856 class B extends A {}
857 class C extends B {}
858 
859 const msg = 'ok';
860 strictEqual(C.method(msg), msg);
861 
862 mock.method(C, C.someTask.name);
863 strictEqual(C.someTask.mock.calls.length, 0);
864 
865 strictEqual(C.method(msg), msg);
866 
867 const call = C.someTask.mock.calls[0];
868 
869 deepStrictEqual(call.arguments, [msg]);
870 strictEqual(call.result, msg);
871 strictEqual(call.target, undefined);
872 strictEqual(call.this, C);
873 
874 strictEqual(C.someTask.mock.restore(), undefined);
875 strictEqual(C.method(msg), msg);
876 strictEqual(C.someTask.mock, undefined);
877 },
878};
879 
880export const spyFunctionsDontAffectThePrototypeChain = {
881 test() {
882 class A {
883 static someTask(msg) {
884 return msg;
885 }
886 }
887 class B extends A {}
888 class C extends B {}
889 
890 const msg = 'ok';
891 
892 const ABeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(
893 A,
894 A.someTask.name
895 );
896 const BBeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(
897 B,
898 B.someTask.name
899 );
900 const CBeforeMockShouldNotHaveDesc = Object.getOwnPropertyDescriptor(
901 C,
902 C.someTask.name
903 );
904 
905 mock.method(C, C.someTask.name);
906 C.someTask(msg);
907 const BAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(
908 B,
909 B.someTask.name
910 );
911 
912 const AAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(
913 A,
914 A.someTask.name
915 );
916 const CAfterMockHasDescriptor = Object.getOwnPropertyDescriptor(
917 C,
918 C.someTask.name
919 );
920 
921 strictEqual(CBeforeMockShouldNotHaveDesc, undefined);
922 ok(CAfterMockHasDescriptor);
923 
924 deepStrictEqual(ABeforeMockIsUnchanged, AAfterMockIsUnchanged);
925 strictEqual(BBeforeMockIsUnchanged, BAfterMockIsUnchanged);
926 strictEqual(BBeforeMockIsUnchanged, undefined);
927 
928 strictEqual(C.someTask.mock.restore(), undefined);
929 const CAfterRestoreKeepsDescriptor = Object.getOwnPropertyDescriptor(
930 C,
931 C.someTask.name
932 );
933 ok(CAfterRestoreKeepsDescriptor);
934 },
935};
936 
937export const mockedFunctionsReportThrownErrors = {
938 test() {
939 const testError = new Error('test error');
940 const fn = mock.fn(() => {
941 throw testError;
942 });
943 
944 throws(fn, /test error/);
945 strictEqual(fn.mock.calls.length, 1);
946 
947 const call = fn.mock.calls[0];
948 
949 deepStrictEqual(call.arguments, []);
950 strictEqual(call.error, testError);
951 strictEqual(call.result, undefined);
952 strictEqual(call.target, undefined);
953 strictEqual(call.this, undefined);
954 },
955};
956 
957export const mockedConstructorsReportThrownErrors = {
958 test() {
959 const testError = new Error('test error');
960 class Clazz {
961 constructor() {
962 throw testError;
963 }
964 }
965 
966 const ctor = mock.fn(Clazz);
967 
968 throws(() => {
969 new ctor();
970 }, /test error/);
971 strictEqual(ctor.mock.calls.length, 1);
972 
973 const call = ctor.mock.calls[0];
974 
975 deepStrictEqual(call.arguments, []);
976 strictEqual(call.error, testError);
977 strictEqual(call.result, undefined);
978 strictEqual(call.target, Clazz);
979 strictEqual(call.this, undefined);
980 },
981};
982 
983export const mocksAFunction = {
984 test() {
985 const sum = (arg1, arg2) => arg1 + arg2;
986 const difference = (arg1, arg2) => arg1 - arg2;
987 const fn = mock.fn(sum, difference);
988 
989 strictEqual(fn.mock.calls.length, 0);
990 strictEqual(fn(3, 4), -1);
991 strictEqual(fn(9, 1), 8);
992 strictEqual(fn.mock.calls.length, 2);
993 
994 let call = fn.mock.calls[0];
995 deepStrictEqual(call.arguments, [3, 4]);
996 strictEqual(call.result, -1);
997 strictEqual(call.target, undefined);
998 strictEqual(call.this, undefined);
999 
1000 call = fn.mock.calls[1];
1001 deepStrictEqual(call.arguments, [9, 1]);
1002 strictEqual(call.result, 8);
1003 strictEqual(call.target, undefined);
1004 strictEqual(call.this, undefined);
1005 
1006 strictEqual(fn.mock.restore(), undefined);
1007 strictEqual(fn(2, 11), 13);
1008 },
1009};
1010 
1011export const mocksAConstructor = {
1012 test() {
1013 class ParentClazz {
1014 constructor(c) {
1015 this.c = c;
1016 }
1017 }
1018 
1019 class Clazz extends ParentClazz {
1020 #privateValue;
1021 
1022 constructor(a, b) {
1023 super(a + b);
1024 this.a = a;
1025 this.#privateValue = b;
1026 }
1027 
1028 getPrivateValue() {
1029 return this.#privateValue;
1030 }
1031 }
1032 
1033 class MockClazz {
1034 constructor(z) {
1035 this.z = z;
1036 }
1037 }
1038 
1039 const ctor = mock.fn(Clazz, MockClazz);
1040 const instance = new ctor(42, 85);
1041 
1042 ok(!(instance instanceof MockClazz));
1043 ok(instance instanceof Clazz);
1044 ok(instance instanceof ParentClazz);
1045 strictEqual(instance.a, undefined);
1046 strictEqual(instance.c, undefined);
1047 strictEqual(instance.z, 42);
1048 strictEqual(ctor.mock.calls.length, 1);
1049 
1050 const call = ctor.mock.calls[0];
1051 
1052 deepStrictEqual(call.arguments, [42, 85]);
1053 strictEqual(call.result, instance);
1054 strictEqual(call.target, Clazz);
1055 strictEqual(call.this, instance);
1056 throws(() => {
1057 instance.getPrivateValue();
1058 }, /TypeError: Cannot read private member #privateValue /);
1059 },
1060};
1061 
1062export const mocksAnObjectMethod = {
1063 test() {
1064 const obj = {
1065 prop: 5,
1066 method(a, b) {
1067 return a + b + this.prop;
1068 },
1069 };
1070 
1071 function mockMethod(a) {
1072 return a + this.prop;
1073 }
1074 
1075 strictEqual(obj.method(1, 3), 9);
1076 mock.method(obj, 'method', mockMethod);
1077 strictEqual(obj.method.mock.calls.length, 0);
1078 strictEqual(obj.method(1, 3), 6);
1079 
1080 const call = obj.method.mock.calls[0];
1081 
1082 deepStrictEqual(call.arguments, [1, 3]);
1083 strictEqual(call.result, 6);
1084 strictEqual(call.target, undefined);
1085 strictEqual(call.this, obj);
1086 
1087 strictEqual(obj.method.mock.restore(), undefined);
1088 strictEqual(obj.method(1, 3), 9);
1089 strictEqual(obj.method.mock, undefined);
1090 },
1091};
1092 
1093export const mocksAGetter = {
1094 test() {
1095 const obj = {
1096 prop: 5,
1097 get method() {
1098 return this.prop;
1099 },
1100 };
1101 
1102 function mockMethod() {
1103 return this.prop - 1;
1104 }
1105 
1106 strictEqual(obj.method, 5);
1107 
1108 const getter = mock.method(obj, 'method', mockMethod, { getter: true });
1109 
1110 strictEqual(getter.mock.calls.length, 0);
1111 strictEqual(obj.method, 4);
1112 
1113 const call = getter.mock.calls[0];
1114 
1115 deepStrictEqual(call.arguments, []);
1116 strictEqual(call.result, 4);
1117 strictEqual(call.target, undefined);
1118 strictEqual(call.this, obj);
1119 
1120 strictEqual(getter.mock.restore(), undefined);
1121 strictEqual(obj.method, 5);
1122 },
1123};
1124 
1125export const mocksASetter = {
1126 test() {
1127 const obj = {
1128 prop: 100,
1129 set method(val) {
1130 this.prop = val;
1131 },
1132 };
1133 
1134 function mockMethod(val) {
1135 this.prop = -val;
1136 }
1137 
1138 strictEqual(obj.prop, 100);
1139 obj.method = 88;
1140 strictEqual(obj.prop, 88);
1141 
1142 const setter = mock.method(obj, 'method', mockMethod, { setter: true });
1143 
1144 strictEqual(setter.mock.calls.length, 0);
1145 obj.method = 77;
1146 strictEqual(obj.prop, -77);
1147 strictEqual(setter.mock.calls.length, 1);
1148 
1149 const call = setter.mock.calls[0];
1150 
1151 deepStrictEqual(call.arguments, [77]);
1152 strictEqual(call.result, undefined);
1153 strictEqual(call.target, undefined);
1154 strictEqual(call.this, obj);
1155 
1156 strictEqual(setter.mock.restore(), undefined);
1157 strictEqual(obj.prop, -77);
1158 obj.method = 65;
1159 strictEqual(obj.prop, 65);
1160 },
1161};
1162 
1163export const mocksAGetterWithSyntaxSugar = {
1164 test() {
1165 const obj = {
1166 prop: 5,
1167 get method() {
1168 return this.prop;
1169 },
1170 };
1171 
1172 function mockMethod() {
1173 return this.prop - 1;
1174 }
1175 const getter = mock.getter(obj, 'method', mockMethod);
1176 strictEqual(getter.mock.calls.length, 0);
1177 strictEqual(obj.method, 4);
1178 
1179 const call = getter.mock.calls[0];
1180 
1181 deepStrictEqual(call.arguments, []);
1182 strictEqual(call.result, 4);
1183 strictEqual(call.target, undefined);
1184 strictEqual(call.this, obj);
1185 
1186 strictEqual(getter.mock.restore(), undefined);
1187 strictEqual(obj.method, 5);
1188 },
1189};
1190 
1191export const mocksASetterWithSyntaxSugar = {
1192 test() {
1193 const obj = {
1194 prop: 100,
1195 set method(val) {
1196 this.prop = val;
1197 },
1198 };
1199 
1200 function mockMethod(val) {
1201 this.prop = -val;
1202 }
1203 
1204 strictEqual(obj.prop, 100);
1205 obj.method = 88;
1206 strictEqual(obj.prop, 88);
1207 
1208 const setter = mock.setter(obj, 'method', mockMethod);
1209 
1210 strictEqual(setter.mock.calls.length, 0);
1211 obj.method = 77;
1212 strictEqual(obj.prop, -77);
1213 strictEqual(setter.mock.calls.length, 1);
1214 
1215 const call = setter.mock.calls[0];
1216 
1217 deepStrictEqual(call.arguments, [77]);
1218 strictEqual(call.result, undefined);
1219 strictEqual(call.target, undefined);
1220 strictEqual(call.this, obj);
1221 
1222 strictEqual(setter.mock.restore(), undefined);
1223 strictEqual(obj.prop, -77);
1224 obj.method = 65;
1225 strictEqual(obj.prop, 65);
1226 },
1227};
1228 
1229export const mockedFunctionsMatchNameAndLength = {
1230 test() {
1231 function getNameAndLength(fn) {
1232 return {
1233 name: Object.getOwnPropertyDescriptor(fn, 'name'),
1234 length: Object.getOwnPropertyDescriptor(fn, 'length'),
1235 };
1236 }
1237 
1238 function func1() {}
1239 const func2 = function (a) {};
1240 const arrow = (a, b, c) => {};
1241 const obj = { method(a, b) {} };
1242 
1243 deepStrictEqual(getNameAndLength(func1), getNameAndLength(mock.fn(func1)));
1244 deepStrictEqual(getNameAndLength(func2), getNameAndLength(mock.fn(func2)));
1245 deepStrictEqual(getNameAndLength(arrow), getNameAndLength(mock.fn(arrow)));
1246 deepStrictEqual(
1247 getNameAndLength(obj.method),
1248 getNameAndLength(mock.method(obj, 'method', func1))
1249 );
1250 },
1251};
1252 
1253export const methodFailsIfMethodCannotBeRedefined = {
1254 test() {
1255 const obj = {
1256 prop: 5,
1257 };
1258 
1259 Object.defineProperty(obj, 'method', {
1260 configurable: false,
1261 value(a, b) {
1262 return a + b + this.prop;
1263 },
1264 });
1265 
1266 function mockMethod(a) {
1267 return a + this.prop;
1268 }
1269 
1270 throws(() => {
1271 mock.method(obj, 'method', mockMethod);
1272 }, /Cannot redefine property: method/);
1273 strictEqual(obj.method(1, 3), 9);
1274 strictEqual(obj.method.mock, undefined);
1275 },
1276};
1277 
1278export const methodFailsIfFieldIsAPropertyInsteadOfAMethod = {
1279 test() {
1280 const obj = {
1281 prop: 5,
1282 method: 100,
1283 };
1284 
1285 function mockMethod(a) {
1286 return a + this.prop;
1287 }
1288 
1289 throws(() => {
1290 mock.method(obj, 'method', mockMethod);
1291 }, /The argument 'methodName' must be a method/);
1292 strictEqual(obj.method, 100);
1293 strictEqual(obj.method.mock, undefined);
1294 },
1295};
1296 
1297export const mocksCanBeRestored = {
1298 test() {
1299 let cnt = 0;
1300 
1301 function addOne() {
1302 cnt++;
1303 return cnt;
1304 }
1305 
1306 function addTwo() {
1307 cnt += 2;
1308 return cnt;
1309 }
1310 
1311 const fn = mock.fn(addOne, addTwo, { times: 2 });
1312 
1313 strictEqual(fn(), 2);
1314 strictEqual(fn(), 4);
1315 strictEqual(fn(), 5);
1316 strictEqual(fn(), 6);
1317 },
1318};
1319 
1320export const mockImplementationCanBeChangedDynamically = {
1321 test() {
1322 let cnt = 0;
1323 
1324 function addOne() {
1325 cnt++;
1326 return cnt;
1327 }
1328 
1329 function addTwo() {
1330 cnt += 2;
1331 return cnt;
1332 }
1333 
1334 function addThree() {
1335 cnt += 3;
1336 return cnt;
1337 }
1338 
1339 function mustNotCall() {
1340 return function () {
1341 throw new Error('This function should not be called');
1342 };
1343 }
1344 
1345 const fn = mock.fn(addOne);
1346 
1347 strictEqual(fn.mock.callCount(), 0);
1348 strictEqual(fn(), 1);
1349 strictEqual(fn(), 2);
1350 strictEqual(fn(), 3);
1351 strictEqual(fn.mock.callCount(), 3);
1352 
1353 fn.mock.mockImplementation(addTwo);
1354 strictEqual(fn(), 5);
1355 strictEqual(fn(), 7);
1356 strictEqual(fn.mock.callCount(), 5);
1357 
1358 fn.mock.restore();
1359 strictEqual(fn(), 8);
1360 strictEqual(fn(), 9);
1361 strictEqual(fn.mock.callCount(), 7);
1362 
1363 throws(() => {
1364 fn.mock.mockImplementationOnce(mustNotCall(), 6);
1365 }, /The value of "onCall" is out of range\. It must be >= 7/);
1366 
1367 fn.mock.mockImplementationOnce(addThree, 7);
1368 fn.mock.mockImplementationOnce(addTwo, 8);
1369 strictEqual(fn(), 12);
1370 strictEqual(fn(), 14);
1371 strictEqual(fn(), 15);
1372 strictEqual(fn.mock.callCount(), 10);
1373 fn.mock.mockImplementationOnce(addThree);
1374 strictEqual(fn(), 18);
1375 strictEqual(fn(), 19);
1376 strictEqual(fn.mock.callCount(), 12);
1377 },
1378};
1379 
1380export const resetMockCalls = {
1381 test() {
1382 const sum = (arg1, arg2) => arg1 + arg2;
1383 const difference = (arg1, arg2) => arg1 - arg2;
1384 const fn = mock.fn(sum, difference);
1385 
1386 strictEqual(fn(1, 2), -1);
1387 strictEqual(fn(2, 1), 1);
1388 strictEqual(fn.mock.calls.length, 2);
1389 strictEqual(fn.mock.callCount(), 2);
1390 
1391 fn.mock.resetCalls();
1392 strictEqual(fn.mock.calls.length, 0);
1393 strictEqual(fn.mock.callCount(), 0);
1394 
1395 strictEqual(fn(3, 2), 1);
1396 },
1397};
1398 
1399export const usesTopLevelMock = {
1400 test() {
1401 function sum(a, b) {
1402 return a + b;
1403 }
1404 
1405 function difference(a, b) {
1406 return a - b;
1407 }
1408 
1409 const fn = mock.fn(sum, difference);
1410 
1411 strictEqual(fn.mock.calls.length, 0);
1412 strictEqual(fn(3, 4), -1);
1413 strictEqual(fn.mock.calls.length, 1);
1414 mock.reset();
1415 strictEqual(fn(3, 4), 7);
1416 strictEqual(fn.mock.calls.length, 2);
1417 },
1418};
1419 
1420export const theGetterAndSetterOptionsCannotBeUsedTogether = {
1421 test() {
1422 throws(() => {
1423 mock.method({}, 'method', { getter: true, setter: true });
1424 }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
1425 },
1426};
1427 
1428export const methodNamesMustBeStringsOrSymbols = {
1429 test() {
1430 const symbol = Symbol();
1431 const obj = {
1432 method() {},
1433 [symbol]() {},
1434 };
1435 
1436 mock.method(obj, 'method');
1437 mock.method(obj, symbol);
1438 
1439 throws(() => {
1440 mock.method(obj, {});
1441 }, /The "methodName" argument must be one of type string or symbol/);
1442 },
1443};
1444 
1445export const theTimesOptionMustBeAnIntegerGreaterOrEqualToOne = {
1446 test() {
1447 throws(() => {
1448 mock.fn({ times: null });
1449 }, /The "options\.times" property must be of type number/);
1450 
1451 throws(() => {
1452 mock.fn({ times: 0 });
1453 }, /The value of "options\.times" is out of range/);
1454 
1455 throws(() => {
1456 mock.fn(() => {}, { times: 3.14159 });
1457 }, /The value of "options\.times" is out of range/);
1458 },
1459};
1460 
1461export const spiesOnAClassPrototypeMethod = {
1462 test() {
1463 class Clazz {
1464 constructor(c) {
1465 this.c = c;
1466 }
1467 
1468 getC() {
1469 return this.c;
1470 }
1471 }
1472 
1473 const instance = new Clazz(85);
1474 
1475 strictEqual(instance.getC(), 85);
1476 mock.method(Clazz.prototype, 'getC');
1477 
1478 strictEqual(instance.getC.mock.calls.length, 0);
1479 strictEqual(instance.getC(), 85);
1480 strictEqual(instance.getC.mock.calls.length, 1);
1481 strictEqual(Clazz.prototype.getC.mock.calls.length, 1);
1482 
1483 const call = instance.getC.mock.calls[0];
1484 deepStrictEqual(call.arguments, []);
1485 strictEqual(call.result, 85);
1486 strictEqual(call.error, undefined);
1487 strictEqual(call.target, undefined);
1488 strictEqual(call.this, instance);
1489 },
1490};
1491 
1492export const getterFailsIfGetterOptionsSetToFalse = {
1493 test() {
1494 throws(() => {
1495 mock.getter({}, 'method', { getter: false });
1496 }, /The property 'options\.getter' cannot be false/);
1497 },
1498};
1499 
1500export const setterFailsIfSetterOptionsSetToFalse = {
1501 test() {
1502 throws(() => {
1503 mock.setter({}, 'method', { setter: false });
1504 }, /The property 'options\.setter' cannot be false/);
1505 },
1506};
1507 
1508export const getterFailsIfSetterOptionsIsTrue = {
1509 test() {
1510 throws(() => {
1511 mock.getter({}, 'method', { setter: true });
1512 }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
1513 },
1514};
1515 
1516export const setterFailsIfGetterOptionsIsTrue = {
1517 test() {
1518 throws(() => {
1519 mock.setter({}, 'method', { getter: true });
1520 }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
1521 },
1522};