• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3const assert = require('node:assert');
4const { mock, test } = require('node:test');
5test('spies on a function', (t) => {
6  const sum = t.mock.fn((arg1, arg2) => {
7    return arg1 + arg2;
8  });
9
10  assert.strictEqual(sum.mock.calls.length, 0);
11  assert.strictEqual(sum(3, 4), 7);
12  assert.strictEqual(sum.call(1000, 9, 1), 10);
13  assert.strictEqual(sum.mock.calls.length, 2);
14
15  let call = sum.mock.calls[0];
16  assert.deepStrictEqual(call.arguments, [3, 4]);
17  assert.strictEqual(call.error, undefined);
18  assert.strictEqual(call.result, 7);
19  assert.strictEqual(call.target, undefined);
20  assert.strictEqual(call.this, undefined);
21
22  call = sum.mock.calls[1];
23  assert.deepStrictEqual(call.arguments, [9, 1]);
24  assert.strictEqual(call.error, undefined);
25  assert.strictEqual(call.result, 10);
26  assert.strictEqual(call.target, undefined);
27  assert.strictEqual(call.this, 1000);
28});
29
30test('spies on a bound function', (t) => {
31  const bound = function(arg1, arg2) {
32    return this + arg1 + arg2;
33  }.bind(50);
34  const sum = t.mock.fn(bound);
35
36  assert.strictEqual(sum.mock.calls.length, 0);
37  assert.strictEqual(sum(3, 4), 57);
38  assert.strictEqual(sum(9, 1), 60);
39  assert.strictEqual(sum.mock.calls.length, 2);
40
41  let call = sum.mock.calls[0];
42  assert.deepStrictEqual(call.arguments, [3, 4]);
43  assert.strictEqual(call.result, 57);
44  assert.strictEqual(call.target, undefined);
45  assert.strictEqual(call.this, undefined);
46
47  call = sum.mock.calls[1];
48  assert.deepStrictEqual(call.arguments, [9, 1]);
49  assert.strictEqual(call.result, 60);
50  assert.strictEqual(call.target, undefined);
51  assert.strictEqual(call.this, undefined);
52});
53
54test('spies on a constructor', (t) => {
55  class ParentClazz {
56    constructor(c) {
57      this.c = c;
58    }
59  }
60
61  class Clazz extends ParentClazz {
62    #privateValue;
63
64    constructor(a, b) {
65      super(a + b);
66      this.a = a;
67      this.#privateValue = b;
68    }
69
70    getPrivateValue() {
71      return this.#privateValue;
72    }
73  }
74
75  const ctor = t.mock.fn(Clazz);
76  const instance = new ctor(42, 85);
77
78  assert(instance instanceof Clazz);
79  assert(instance instanceof ParentClazz);
80  assert.strictEqual(instance.a, 42);
81  assert.strictEqual(instance.getPrivateValue(), 85);
82  assert.strictEqual(instance.c, 127);
83  assert.strictEqual(ctor.mock.calls.length, 1);
84
85  const call = ctor.mock.calls[0];
86
87  assert.deepStrictEqual(call.arguments, [42, 85]);
88  assert.strictEqual(call.error, undefined);
89  assert.strictEqual(call.result, instance);
90  assert.strictEqual(call.target, Clazz);
91  assert.strictEqual(call.this, instance);
92});
93
94test('a no-op spy function is created by default', (t) => {
95  const fn = t.mock.fn();
96
97  assert.strictEqual(fn.mock.calls.length, 0);
98  assert.strictEqual(fn(3, 4), undefined);
99  assert.strictEqual(fn.mock.calls.length, 1);
100
101  const call = fn.mock.calls[0];
102  assert.deepStrictEqual(call.arguments, [3, 4]);
103  assert.strictEqual(call.result, undefined);
104  assert.strictEqual(call.target, undefined);
105  assert.strictEqual(call.this, undefined);
106});
107
108test('internal no-op function can be reused', (t) => {
109  const fn1 = t.mock.fn();
110  fn1.prop = true;
111  const fn2 = t.mock.fn();
112
113  fn1(1);
114  fn2(2);
115  fn1(3);
116
117  assert.notStrictEqual(fn1.mock, fn2.mock);
118  assert.strictEqual(fn1.mock.calls.length, 2);
119  assert.strictEqual(fn2.mock.calls.length, 1);
120  assert.strictEqual(fn1.prop, true);
121  assert.strictEqual(fn2.prop, undefined);
122});
123
124test('functions can be mocked multiple times at once', (t) => {
125  function sum(a, b) {
126    return a + b;
127  }
128
129  function difference(a, b) {
130    return a - b;
131  }
132
133  function product(a, b) {
134    return a * b;
135  }
136
137  const fn1 = t.mock.fn(sum, difference);
138  const fn2 = t.mock.fn(sum, product);
139
140  assert.strictEqual(fn1(5, 3), 2);
141  assert.strictEqual(fn2(5, 3), 15);
142  assert.strictEqual(fn2(4, 2), 8);
143  assert(!('mock' in sum));
144  assert(!('mock' in difference));
145  assert(!('mock' in product));
146  assert.notStrictEqual(fn1.mock, fn2.mock);
147  assert.strictEqual(fn1.mock.calls.length, 1);
148  assert.strictEqual(fn2.mock.calls.length, 2);
149});
150
151test('internal no-op function can be reused as methods', (t) => {
152  const obj = {
153    _foo: 5,
154    _bar: 9,
155    foo() {
156      return this._foo;
157    },
158    bar() {
159      return this._bar;
160    },
161  };
162
163  t.mock.method(obj, 'foo');
164  obj.foo.prop = true;
165  t.mock.method(obj, 'bar');
166  assert.strictEqual(obj.foo(), 5);
167  assert.strictEqual(obj.bar(), 9);
168  assert.strictEqual(obj.bar(), 9);
169  assert.notStrictEqual(obj.foo.mock, obj.bar.mock);
170  assert.strictEqual(obj.foo.mock.calls.length, 1);
171  assert.strictEqual(obj.bar.mock.calls.length, 2);
172  assert.strictEqual(obj.foo.prop, true);
173  assert.strictEqual(obj.bar.prop, undefined);
174});
175
176test('methods can be mocked multiple times but not at the same time', (t) => {
177  const obj = {
178    offset: 3,
179    sum(a, b) {
180      return this.offset + a + b;
181    },
182  };
183
184  function difference(a, b) {
185    return this.offset + (a - b);
186  }
187
188  function product(a, b) {
189    return this.offset + a * b;
190  }
191
192  const originalSum = obj.sum;
193  const fn1 = t.mock.method(obj, 'sum', difference);
194
195  assert.strictEqual(obj.sum(5, 3), 5);
196  assert.strictEqual(obj.sum(5, 1), 7);
197  assert.strictEqual(obj.sum, fn1);
198  assert.notStrictEqual(fn1.mock, undefined);
199  assert.strictEqual(originalSum.mock, undefined);
200  assert.strictEqual(difference.mock, undefined);
201  assert.strictEqual(product.mock, undefined);
202  assert.strictEqual(fn1.mock.calls.length, 2);
203
204  const fn2 = t.mock.method(obj, 'sum', product);
205
206  assert.strictEqual(obj.sum(5, 3), 18);
207  assert.strictEqual(obj.sum, fn2);
208  assert.notStrictEqual(fn1, fn2);
209  assert.strictEqual(fn2.mock.calls.length, 1);
210
211  obj.sum.mock.restore();
212  assert.strictEqual(obj.sum, fn1);
213  obj.sum.mock.restore();
214  assert.strictEqual(obj.sum, originalSum);
215  assert.strictEqual(obj.sum.mock, undefined);
216});
217
218test('spies on an object method', (t) => {
219  const obj = {
220    prop: 5,
221    method(a, b) {
222      return a + b + this.prop;
223    },
224  };
225
226  assert.strictEqual(obj.method(1, 3), 9);
227  t.mock.method(obj, 'method');
228  assert.strictEqual(obj.method.mock.calls.length, 0);
229  assert.strictEqual(obj.method(1, 3), 9);
230
231  const call = obj.method.mock.calls[0];
232
233  assert.deepStrictEqual(call.arguments, [1, 3]);
234  assert.strictEqual(call.result, 9);
235  assert.strictEqual(call.target, undefined);
236  assert.strictEqual(call.this, obj);
237
238  assert.strictEqual(obj.method.mock.restore(), undefined);
239  assert.strictEqual(obj.method(1, 3), 9);
240  assert.strictEqual(obj.method.mock, undefined);
241});
242
243test('spies on a getter', (t) => {
244  const obj = {
245    prop: 5,
246    get method() {
247      return this.prop;
248    },
249  };
250
251  assert.strictEqual(obj.method, 5);
252
253  const getter = t.mock.method(obj, 'method', { getter: true });
254
255  assert.strictEqual(getter.mock.calls.length, 0);
256  assert.strictEqual(obj.method, 5);
257
258  const call = getter.mock.calls[0];
259
260  assert.deepStrictEqual(call.arguments, []);
261  assert.strictEqual(call.result, 5);
262  assert.strictEqual(call.target, undefined);
263  assert.strictEqual(call.this, obj);
264
265  assert.strictEqual(getter.mock.restore(), undefined);
266  assert.strictEqual(obj.method, 5);
267});
268
269test('spies on a setter', (t) => {
270  const obj = {
271    prop: 100,
272    // eslint-disable-next-line accessor-pairs
273    set method(val) {
274      this.prop = val;
275    },
276  };
277
278  assert.strictEqual(obj.prop, 100);
279  obj.method = 88;
280  assert.strictEqual(obj.prop, 88);
281
282  const setter = t.mock.method(obj, 'method', { setter: true });
283
284  assert.strictEqual(setter.mock.calls.length, 0);
285  obj.method = 77;
286  assert.strictEqual(obj.prop, 77);
287  assert.strictEqual(setter.mock.calls.length, 1);
288
289  const call = setter.mock.calls[0];
290
291  assert.deepStrictEqual(call.arguments, [77]);
292  assert.strictEqual(call.result, undefined);
293  assert.strictEqual(call.target, undefined);
294  assert.strictEqual(call.this, obj);
295
296  assert.strictEqual(setter.mock.restore(), undefined);
297  assert.strictEqual(obj.prop, 77);
298  obj.method = 65;
299  assert.strictEqual(obj.prop, 65);
300});
301
302test('spy functions can be bound', (t) => {
303  const sum = t.mock.fn(function(arg1, arg2) {
304    return this + arg1 + arg2;
305  });
306  const bound = sum.bind(1000);
307
308  assert.strictEqual(bound(9, 1), 1010);
309  assert.strictEqual(sum.mock.calls.length, 1);
310
311  const call = sum.mock.calls[0];
312  assert.deepStrictEqual(call.arguments, [9, 1]);
313  assert.strictEqual(call.result, 1010);
314  assert.strictEqual(call.target, undefined);
315  assert.strictEqual(call.this, 1000);
316
317  assert.strictEqual(sum.mock.restore(), undefined);
318  assert.strictEqual(sum.bind(0)(2, 11), 13);
319});
320
321test('mocks prototype methods on an instance', async (t) => {
322  class Runner {
323    async someTask(msg) {
324      return Promise.resolve(msg);
325    }
326
327    async method(msg) {
328      await this.someTask(msg);
329      return msg;
330    }
331  }
332  const msg = 'ok';
333  const obj = new Runner();
334  assert.strictEqual(await obj.method(msg), msg);
335
336  t.mock.method(obj, obj.someTask.name);
337  assert.strictEqual(obj.someTask.mock.calls.length, 0);
338
339  assert.strictEqual(await obj.method(msg), msg);
340
341  const call = obj.someTask.mock.calls[0];
342
343  assert.deepStrictEqual(call.arguments, [msg]);
344  assert.strictEqual(await call.result, msg);
345  assert.strictEqual(call.target, undefined);
346  assert.strictEqual(call.this, obj);
347
348  const obj2 = new Runner();
349  // Ensure that a brand new instance is not mocked
350  assert.strictEqual(
351    obj2.someTask.mock,
352    undefined
353  );
354
355  assert.strictEqual(obj.someTask.mock.restore(), undefined);
356  assert.strictEqual(await obj.method(msg), msg);
357  assert.strictEqual(obj.someTask.mock, undefined);
358  assert.strictEqual(Runner.prototype.someTask.mock, undefined);
359});
360
361test('spies on async static class methods', async (t) => {
362  class Runner {
363    static async someTask(msg) {
364      return Promise.resolve(msg);
365    }
366
367    static async method(msg) {
368      await this.someTask(msg);
369      return msg;
370    }
371  }
372  const msg = 'ok';
373  assert.strictEqual(await Runner.method(msg), msg);
374
375  t.mock.method(Runner, Runner.someTask.name);
376  assert.strictEqual(Runner.someTask.mock.calls.length, 0);
377
378  assert.strictEqual(await Runner.method(msg), msg);
379
380  const call = Runner.someTask.mock.calls[0];
381
382  assert.deepStrictEqual(call.arguments, [msg]);
383  assert.strictEqual(await call.result, msg);
384  assert.strictEqual(call.target, undefined);
385  assert.strictEqual(call.this, Runner);
386
387  assert.strictEqual(Runner.someTask.mock.restore(), undefined);
388  assert.strictEqual(await Runner.method(msg), msg);
389  assert.strictEqual(Runner.someTask.mock, undefined);
390  assert.strictEqual(Runner.prototype.someTask, undefined);
391
392});
393
394test('given null to a mock.method it throws a invalid argument error', (t) => {
395  assert.throws(() => t.mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });
396});
397
398test('it should throw given an inexistent property on a object instance', (t) => {
399  assert.throws(() => t.mock.method({ abc: 0 }, 'non-existent'), {
400    code: 'ERR_INVALID_ARG_VALUE'
401  });
402});
403
404test('spy functions can be used on classes inheritance', (t) => {
405  // Makes sure that having a null-prototype doesn't throw our system off
406  class A extends null {
407    static someTask(msg) {
408      return msg;
409    }
410    static method(msg) {
411      return this.someTask(msg);
412    }
413  }
414  class B extends A {}
415  class C extends B {}
416
417  const msg = 'ok';
418  assert.strictEqual(C.method(msg), msg);
419
420  t.mock.method(C, C.someTask.name);
421  assert.strictEqual(C.someTask.mock.calls.length, 0);
422
423  assert.strictEqual(C.method(msg), msg);
424
425  const call = C.someTask.mock.calls[0];
426
427  assert.deepStrictEqual(call.arguments, [msg]);
428  assert.strictEqual(call.result, msg);
429  assert.strictEqual(call.target, undefined);
430  assert.strictEqual(call.this, C);
431
432  assert.strictEqual(C.someTask.mock.restore(), undefined);
433  assert.strictEqual(C.method(msg), msg);
434  assert.strictEqual(C.someTask.mock, undefined);
435});
436
437test('spy functions don\'t affect the prototype chain', (t) => {
438
439  class A {
440    static someTask(msg) {
441      return msg;
442    }
443  }
444  class B extends A {}
445  class C extends B {}
446
447  const msg = 'ok';
448
449  const ABeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name);
450  const BBeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name);
451  const CBeforeMockShouldNotHaveDesc = Object.getOwnPropertyDescriptor(C, C.someTask.name);
452
453  t.mock.method(C, C.someTask.name);
454  C.someTask(msg);
455  const BAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name);
456
457  const AAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name);
458  const CAfterMockHasDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name);
459
460  assert.strictEqual(CBeforeMockShouldNotHaveDesc, undefined);
461  assert.ok(CAfterMockHasDescriptor);
462
463  assert.deepStrictEqual(ABeforeMockIsUnchanged, AAfterMockIsUnchanged);
464  assert.strictEqual(BBeforeMockIsUnchanged, BAfterMockIsUnchanged);
465  assert.strictEqual(BBeforeMockIsUnchanged, undefined);
466
467  assert.strictEqual(C.someTask.mock.restore(), undefined);
468  const CAfterRestoreKeepsDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name);
469  assert.ok(CAfterRestoreKeepsDescriptor);
470});
471
472test('mocked functions report thrown errors', (t) => {
473  const testError = new Error('test error');
474  const fn = t.mock.fn(() => {
475    throw testError;
476  });
477
478  assert.throws(fn, /test error/);
479  assert.strictEqual(fn.mock.calls.length, 1);
480
481  const call = fn.mock.calls[0];
482
483  assert.deepStrictEqual(call.arguments, []);
484  assert.strictEqual(call.error, testError);
485  assert.strictEqual(call.result, undefined);
486  assert.strictEqual(call.target, undefined);
487  assert.strictEqual(call.this, undefined);
488});
489
490test('mocked constructors report thrown errors', (t) => {
491  const testError = new Error('test error');
492  class Clazz {
493    constructor() {
494      throw testError;
495    }
496  }
497
498  const ctor = t.mock.fn(Clazz);
499
500  assert.throws(() => {
501    new ctor();
502  }, /test error/);
503  assert.strictEqual(ctor.mock.calls.length, 1);
504
505  const call = ctor.mock.calls[0];
506
507  assert.deepStrictEqual(call.arguments, []);
508  assert.strictEqual(call.error, testError);
509  assert.strictEqual(call.result, undefined);
510  assert.strictEqual(call.target, Clazz);
511  assert.strictEqual(call.this, undefined);
512});
513
514test('mocks a function', (t) => {
515  const sum = (arg1, arg2) => arg1 + arg2;
516  const difference = (arg1, arg2) => arg1 - arg2;
517  const fn = t.mock.fn(sum, difference);
518
519  assert.strictEqual(fn.mock.calls.length, 0);
520  assert.strictEqual(fn(3, 4), -1);
521  assert.strictEqual(fn(9, 1), 8);
522  assert.strictEqual(fn.mock.calls.length, 2);
523
524  let call = fn.mock.calls[0];
525  assert.deepStrictEqual(call.arguments, [3, 4]);
526  assert.strictEqual(call.result, -1);
527  assert.strictEqual(call.target, undefined);
528  assert.strictEqual(call.this, undefined);
529
530  call = fn.mock.calls[1];
531  assert.deepStrictEqual(call.arguments, [9, 1]);
532  assert.strictEqual(call.result, 8);
533  assert.strictEqual(call.target, undefined);
534  assert.strictEqual(call.this, undefined);
535
536  assert.strictEqual(fn.mock.restore(), undefined);
537  assert.strictEqual(fn(2, 11), 13);
538});
539
540test('mocks a constructor', (t) => {
541  class ParentClazz {
542    constructor(c) {
543      this.c = c;
544    }
545  }
546
547  class Clazz extends ParentClazz {
548    #privateValue;
549
550    constructor(a, b) {
551      super(a + b);
552      this.a = a;
553      this.#privateValue = b;
554    }
555
556    getPrivateValue() {
557      return this.#privateValue;
558    }
559  }
560
561  class MockClazz {
562    #privateValue;
563
564    constructor(z) {
565      this.z = z;
566    }
567  }
568
569  const ctor = t.mock.fn(Clazz, MockClazz);
570  const instance = new ctor(42, 85);
571
572  assert(!(instance instanceof MockClazz));
573  assert(instance instanceof Clazz);
574  assert(instance instanceof ParentClazz);
575  assert.strictEqual(instance.a, undefined);
576  assert.strictEqual(instance.c, undefined);
577  assert.strictEqual(instance.z, 42);
578  assert.strictEqual(ctor.mock.calls.length, 1);
579
580  const call = ctor.mock.calls[0];
581
582  assert.deepStrictEqual(call.arguments, [42, 85]);
583  assert.strictEqual(call.result, instance);
584  assert.strictEqual(call.target, Clazz);
585  assert.strictEqual(call.this, instance);
586  assert.throws(() => {
587    instance.getPrivateValue();
588  }, /TypeError: Cannot read private member #privateValue /);
589});
590
591test('mocks an object method', (t) => {
592  const obj = {
593    prop: 5,
594    method(a, b) {
595      return a + b + this.prop;
596    },
597  };
598
599  function mockMethod(a) {
600    return a + this.prop;
601  }
602
603  assert.strictEqual(obj.method(1, 3), 9);
604  t.mock.method(obj, 'method', mockMethod);
605  assert.strictEqual(obj.method.mock.calls.length, 0);
606  assert.strictEqual(obj.method(1, 3), 6);
607
608  const call = obj.method.mock.calls[0];
609
610  assert.deepStrictEqual(call.arguments, [1, 3]);
611  assert.strictEqual(call.result, 6);
612  assert.strictEqual(call.target, undefined);
613  assert.strictEqual(call.this, obj);
614
615  assert.strictEqual(obj.method.mock.restore(), undefined);
616  assert.strictEqual(obj.method(1, 3), 9);
617  assert.strictEqual(obj.method.mock, undefined);
618});
619
620test('mocks a getter', (t) => {
621  const obj = {
622    prop: 5,
623    get method() {
624      return this.prop;
625    },
626  };
627
628  function mockMethod() {
629    return this.prop - 1;
630  }
631
632  assert.strictEqual(obj.method, 5);
633
634  const getter = t.mock.method(obj, 'method', mockMethod, { getter: true });
635
636  assert.strictEqual(getter.mock.calls.length, 0);
637  assert.strictEqual(obj.method, 4);
638
639  const call = getter.mock.calls[0];
640
641  assert.deepStrictEqual(call.arguments, []);
642  assert.strictEqual(call.result, 4);
643  assert.strictEqual(call.target, undefined);
644  assert.strictEqual(call.this, obj);
645
646  assert.strictEqual(getter.mock.restore(), undefined);
647  assert.strictEqual(obj.method, 5);
648});
649
650test('mocks a setter', (t) => {
651  const obj = {
652    prop: 100,
653    // eslint-disable-next-line accessor-pairs
654    set method(val) {
655      this.prop = val;
656    },
657  };
658
659  function mockMethod(val) {
660    this.prop = -val;
661  }
662
663  assert.strictEqual(obj.prop, 100);
664  obj.method = 88;
665  assert.strictEqual(obj.prop, 88);
666
667  const setter = t.mock.method(obj, 'method', mockMethod, { setter: true });
668
669  assert.strictEqual(setter.mock.calls.length, 0);
670  obj.method = 77;
671  assert.strictEqual(obj.prop, -77);
672  assert.strictEqual(setter.mock.calls.length, 1);
673
674  const call = setter.mock.calls[0];
675
676  assert.deepStrictEqual(call.arguments, [77]);
677  assert.strictEqual(call.result, undefined);
678  assert.strictEqual(call.target, undefined);
679  assert.strictEqual(call.this, obj);
680
681  assert.strictEqual(setter.mock.restore(), undefined);
682  assert.strictEqual(obj.prop, -77);
683  obj.method = 65;
684  assert.strictEqual(obj.prop, 65);
685});
686
687test('mocks a getter with syntax sugar', (t) => {
688  const obj = {
689    prop: 5,
690    get method() {
691      return this.prop;
692    },
693  };
694
695  function mockMethod() {
696    return this.prop - 1;
697  }
698  const getter = t.mock.getter(obj, 'method', mockMethod);
699  assert.strictEqual(getter.mock.calls.length, 0);
700  assert.strictEqual(obj.method, 4);
701
702  const call = getter.mock.calls[0];
703
704  assert.deepStrictEqual(call.arguments, []);
705  assert.strictEqual(call.result, 4);
706  assert.strictEqual(call.target, undefined);
707  assert.strictEqual(call.this, obj);
708
709  assert.strictEqual(getter.mock.restore(), undefined);
710  assert.strictEqual(obj.method, 5);
711});
712
713test('mocks a setter with syntax sugar', (t) => {
714  const obj = {
715    prop: 100,
716    // eslint-disable-next-line accessor-pairs
717    set method(val) {
718      this.prop = val;
719    },
720  };
721
722  function mockMethod(val) {
723    this.prop = -val;
724  }
725
726  assert.strictEqual(obj.prop, 100);
727  obj.method = 88;
728  assert.strictEqual(obj.prop, 88);
729
730  const setter = t.mock.setter(obj, 'method', mockMethod);
731
732  assert.strictEqual(setter.mock.calls.length, 0);
733  obj.method = 77;
734  assert.strictEqual(obj.prop, -77);
735  assert.strictEqual(setter.mock.calls.length, 1);
736
737  const call = setter.mock.calls[0];
738
739  assert.deepStrictEqual(call.arguments, [77]);
740  assert.strictEqual(call.result, undefined);
741  assert.strictEqual(call.target, undefined);
742  assert.strictEqual(call.this, obj);
743
744  assert.strictEqual(setter.mock.restore(), undefined);
745  assert.strictEqual(obj.prop, -77);
746  obj.method = 65;
747  assert.strictEqual(obj.prop, 65);
748});
749
750test('mocked functions match name and length', (t) => {
751  function getNameAndLength(fn) {
752    return {
753      name: Object.getOwnPropertyDescriptor(fn, 'name'),
754      length: Object.getOwnPropertyDescriptor(fn, 'length'),
755    };
756  }
757
758  function func1() {}
759  const func2 = function(a) {}; // eslint-disable-line func-style
760  const arrow = (a, b, c) => {};
761  const obj = { method(a, b) {} };
762
763  assert.deepStrictEqual(
764    getNameAndLength(func1),
765    getNameAndLength(t.mock.fn(func1))
766  );
767  assert.deepStrictEqual(
768    getNameAndLength(func2),
769    getNameAndLength(t.mock.fn(func2))
770  );
771  assert.deepStrictEqual(
772    getNameAndLength(arrow),
773    getNameAndLength(t.mock.fn(arrow))
774  );
775  assert.deepStrictEqual(
776    getNameAndLength(obj.method),
777    getNameAndLength(t.mock.method(obj, 'method', func1))
778  );
779});
780
781test('method() fails if method cannot be redefined', (t) => {
782  const obj = {
783    prop: 5,
784  };
785
786  Object.defineProperty(obj, 'method', {
787    configurable: false,
788    value(a, b) {
789      return a + b + this.prop;
790    }
791  });
792
793  function mockMethod(a) {
794    return a + this.prop;
795  }
796
797  assert.throws(() => {
798    t.mock.method(obj, 'method', mockMethod);
799  }, /Cannot redefine property: method/);
800  assert.strictEqual(obj.method(1, 3), 9);
801  assert.strictEqual(obj.method.mock, undefined);
802});
803
804test('method() fails if field is a property instead of a method', (t) => {
805  const obj = {
806    prop: 5,
807    method: 100,
808  };
809
810  function mockMethod(a) {
811    return a + this.prop;
812  }
813
814  assert.throws(() => {
815    t.mock.method(obj, 'method', mockMethod);
816  }, /The argument 'methodName' must be a method/);
817  assert.strictEqual(obj.method, 100);
818  assert.strictEqual(obj.method.mock, undefined);
819});
820
821test('mocks can be auto-restored', (t) => {
822  let cnt = 0;
823
824  function addOne() {
825    cnt++;
826    return cnt;
827  }
828
829  function addTwo() {
830    cnt += 2;
831    return cnt;
832  }
833
834  const fn = t.mock.fn(addOne, addTwo, { times: 2 });
835
836  assert.strictEqual(fn(), 2);
837  assert.strictEqual(fn(), 4);
838  assert.strictEqual(fn(), 5);
839  assert.strictEqual(fn(), 6);
840});
841
842test('mock implementation can be changed dynamically', (t) => {
843  let cnt = 0;
844
845  function addOne() {
846    cnt++;
847    return cnt;
848  }
849
850  function addTwo() {
851    cnt += 2;
852    return cnt;
853  }
854
855  function addThree() {
856    cnt += 3;
857    return cnt;
858  }
859
860  const fn = t.mock.fn(addOne);
861
862  assert.strictEqual(fn.mock.callCount(), 0);
863  assert.strictEqual(fn(), 1);
864  assert.strictEqual(fn(), 2);
865  assert.strictEqual(fn(), 3);
866  assert.strictEqual(fn.mock.callCount(), 3);
867
868  fn.mock.mockImplementation(addTwo);
869  assert.strictEqual(fn(), 5);
870  assert.strictEqual(fn(), 7);
871  assert.strictEqual(fn.mock.callCount(), 5);
872
873  fn.mock.restore();
874  assert.strictEqual(fn(), 8);
875  assert.strictEqual(fn(), 9);
876  assert.strictEqual(fn.mock.callCount(), 7);
877
878  assert.throws(() => {
879    fn.mock.mockImplementationOnce(common.mustNotCall(), 6);
880  }, /The value of "onCall" is out of range\. It must be >= 7/);
881
882  fn.mock.mockImplementationOnce(addThree, 7);
883  fn.mock.mockImplementationOnce(addTwo, 8);
884  assert.strictEqual(fn(), 12);
885  assert.strictEqual(fn(), 14);
886  assert.strictEqual(fn(), 15);
887  assert.strictEqual(fn.mock.callCount(), 10);
888  fn.mock.mockImplementationOnce(addThree);
889  assert.strictEqual(fn(), 18);
890  assert.strictEqual(fn(), 19);
891  assert.strictEqual(fn.mock.callCount(), 12);
892});
893
894test('local mocks are auto restored after the test finishes', async (t) => {
895  const obj = {
896    foo() {},
897    bar() {},
898  };
899  const originalFoo = obj.foo;
900  const originalBar = obj.bar;
901
902  assert.strictEqual(originalFoo, obj.foo);
903  assert.strictEqual(originalBar, obj.bar);
904
905  const mockFoo = t.mock.method(obj, 'foo');
906
907  assert.strictEqual(mockFoo, obj.foo);
908  assert.notStrictEqual(originalFoo, obj.foo);
909  assert.strictEqual(originalBar, obj.bar);
910
911  t.beforeEach(() => {
912    assert.strictEqual(mockFoo, obj.foo);
913    assert.strictEqual(originalBar, obj.bar);
914  });
915
916  t.afterEach(() => {
917    assert.strictEqual(mockFoo, obj.foo);
918    assert.notStrictEqual(originalBar, obj.bar);
919  });
920
921  await t.test('creates mocks that are auto restored', (t) => {
922    const mockBar = t.mock.method(obj, 'bar');
923
924    assert.strictEqual(mockFoo, obj.foo);
925    assert.strictEqual(mockBar, obj.bar);
926    assert.notStrictEqual(originalBar, obj.bar);
927  });
928
929  assert.strictEqual(mockFoo, obj.foo);
930  assert.strictEqual(originalBar, obj.bar);
931});
932
933test('reset mock calls', (t) => {
934  const sum = (arg1, arg2) => arg1 + arg2;
935  const difference = (arg1, arg2) => arg1 - arg2;
936  const fn = t.mock.fn(sum, difference);
937
938  assert.strictEqual(fn(1, 2), -1);
939  assert.strictEqual(fn(2, 1), 1);
940  assert.strictEqual(fn.mock.calls.length, 2);
941  assert.strictEqual(fn.mock.callCount(), 2);
942
943  fn.mock.resetCalls();
944  assert.strictEqual(fn.mock.calls.length, 0);
945  assert.strictEqual(fn.mock.callCount(), 0);
946
947  assert.strictEqual(fn(3, 2), 1);
948});
949
950test('uses top level mock', () => {
951  function sum(a, b) {
952    return a + b;
953  }
954
955  function difference(a, b) {
956    return a - b;
957  }
958
959  const fn = mock.fn(sum, difference);
960
961  assert.strictEqual(fn.mock.calls.length, 0);
962  assert.strictEqual(fn(3, 4), -1);
963  assert.strictEqual(fn.mock.calls.length, 1);
964  mock.reset();
965  assert.strictEqual(fn(3, 4), 7);
966  assert.strictEqual(fn.mock.calls.length, 2);
967});
968
969test('the getter and setter options cannot be used together', (t) => {
970  assert.throws(() => {
971    t.mock.method({}, 'method', { getter: true, setter: true });
972  }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
973});
974
975test('method names must be strings or symbols', (t) => {
976  const symbol = Symbol();
977  const obj = {
978    method() {},
979    [symbol]() {},
980  };
981
982  t.mock.method(obj, 'method');
983  t.mock.method(obj, symbol);
984
985  assert.throws(() => {
986    t.mock.method(obj, {});
987  }, /The "methodName" argument must be one of type string or symbol/);
988});
989
990test('the times option must be an integer >= 1', (t) => {
991  assert.throws(() => {
992    t.mock.fn({ times: null });
993  }, /The "options\.times" property must be of type number/);
994
995  assert.throws(() => {
996    t.mock.fn({ times: 0 });
997  }, /The value of "options\.times" is out of range/);
998
999  assert.throws(() => {
1000    t.mock.fn(() => {}, { times: 3.14159 });
1001  }, /The value of "options\.times" is out of range/);
1002});
1003
1004test('spies on a class prototype method', (t) => {
1005  class Clazz {
1006    constructor(c) {
1007      this.c = c;
1008    }
1009
1010    getC() {
1011      return this.c;
1012    }
1013  }
1014
1015  const instance = new Clazz(85);
1016
1017  assert.strictEqual(instance.getC(), 85);
1018  t.mock.method(Clazz.prototype, 'getC');
1019
1020  assert.strictEqual(instance.getC.mock.calls.length, 0);
1021  assert.strictEqual(instance.getC(), 85);
1022  assert.strictEqual(instance.getC.mock.calls.length, 1);
1023  assert.strictEqual(Clazz.prototype.getC.mock.calls.length, 1);
1024
1025  const call = instance.getC.mock.calls[0];
1026  assert.deepStrictEqual(call.arguments, []);
1027  assert.strictEqual(call.result, 85);
1028  assert.strictEqual(call.error, undefined);
1029  assert.strictEqual(call.target, undefined);
1030  assert.strictEqual(call.this, instance);
1031});
1032
1033test('getter() fails if getter options set to false', (t) => {
1034  assert.throws(() => {
1035    t.mock.getter({}, 'method', { getter: false });
1036  }, /The property 'options\.getter' cannot be false/);
1037});
1038
1039test('setter() fails if setter options set to false', (t) => {
1040  assert.throws(() => {
1041    t.mock.setter({}, 'method', { setter: false });
1042  }, /The property 'options\.setter' cannot be false/);
1043});
1044
1045test('getter() fails if setter options is true', (t) => {
1046  assert.throws(() => {
1047    t.mock.getter({}, 'method', { setter: true });
1048  }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
1049});
1050
1051test('setter() fails if getter options is true', (t) => {
1052  assert.throws(() => {
1053    t.mock.setter({}, 'method', { getter: true });
1054  }, /The property 'options\.setter' cannot be used with 'options\.getter'/);
1055});
1056