UNPKG

23.7 kBJavaScriptView Raw
1'use strict';
2
3function _defineProperty(obj, key, value) {
4 if (key in obj) {
5 Object.defineProperty(obj, key, {
6 value: value,
7 enumerable: true,
8 configurable: true,
9 writable: true
10 });
11 } else {
12 obj[key] = value;
13 }
14 return obj;
15}
16
17/**
18 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
19 *
20 * This source code is licensed under the MIT license found in the
21 * LICENSE file in the root directory of this source tree.
22 */
23
24/**
25 * Possible types of a MockFunctionResult.
26 * 'return': The call completed by returning normally.
27 * 'throw': The call completed by throwing a value.
28 * 'incomplete': The call has not completed yet. This is possible if you read
29 * the mock function result from within the mock function itself
30 * (or a function called by the mock function).
31 */
32
33/**
34 * Represents the result of a single call to a mock function.
35 */
36// see https://github.com/Microsoft/TypeScript/issues/25215
37const MOCK_CONSTRUCTOR_NAME = 'mockConstructor';
38const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
39const FUNCTION_NAME_RESERVED_REPLACE = new RegExp(
40 FUNCTION_NAME_RESERVED_PATTERN.source,
41 'g'
42);
43const RESERVED_KEYWORDS = new Set([
44 'arguments',
45 'await',
46 'break',
47 'case',
48 'catch',
49 'class',
50 'const',
51 'continue',
52 'debugger',
53 'default',
54 'delete',
55 'do',
56 'else',
57 'enum',
58 'eval',
59 'export',
60 'extends',
61 'false',
62 'finally',
63 'for',
64 'function',
65 'if',
66 'implements',
67 'import',
68 'in',
69 'instanceof',
70 'interface',
71 'let',
72 'new',
73 'null',
74 'package',
75 'private',
76 'protected',
77 'public',
78 'return',
79 'static',
80 'super',
81 'switch',
82 'this',
83 'throw',
84 'true',
85 'try',
86 'typeof',
87 'var',
88 'void',
89 'while',
90 'with',
91 'yield'
92]);
93
94function matchArity(fn, length) {
95 let mockConstructor;
96
97 switch (length) {
98 case 1:
99 mockConstructor = function (_a) {
100 return fn.apply(this, arguments);
101 };
102
103 break;
104
105 case 2:
106 mockConstructor = function (_a, _b) {
107 return fn.apply(this, arguments);
108 };
109
110 break;
111
112 case 3:
113 mockConstructor = function (_a, _b, _c) {
114 return fn.apply(this, arguments);
115 };
116
117 break;
118
119 case 4:
120 mockConstructor = function (_a, _b, _c, _d) {
121 return fn.apply(this, arguments);
122 };
123
124 break;
125
126 case 5:
127 mockConstructor = function (_a, _b, _c, _d, _e) {
128 return fn.apply(this, arguments);
129 };
130
131 break;
132
133 case 6:
134 mockConstructor = function (_a, _b, _c, _d, _e, _f) {
135 return fn.apply(this, arguments);
136 };
137
138 break;
139
140 case 7:
141 mockConstructor = function (_a, _b, _c, _d, _e, _f, _g) {
142 return fn.apply(this, arguments);
143 };
144
145 break;
146
147 case 8:
148 mockConstructor = function (_a, _b, _c, _d, _e, _f, _g, _h) {
149 return fn.apply(this, arguments);
150 };
151
152 break;
153
154 case 9:
155 mockConstructor = function (_a, _b, _c, _d, _e, _f, _g, _h, _i) {
156 return fn.apply(this, arguments);
157 };
158
159 break;
160
161 default:
162 mockConstructor = function () {
163 return fn.apply(this, arguments);
164 };
165
166 break;
167 }
168
169 return mockConstructor;
170}
171
172function getObjectType(value) {
173 return Object.prototype.toString.apply(value).slice(8, -1);
174}
175
176function getType(ref) {
177 const typeName = getObjectType(ref);
178
179 if (
180 typeName === 'Function' ||
181 typeName === 'AsyncFunction' ||
182 typeName === 'GeneratorFunction'
183 ) {
184 return 'function';
185 } else if (Array.isArray(ref)) {
186 return 'array';
187 } else if (typeName === 'Object') {
188 return 'object';
189 } else if (
190 typeName === 'Number' ||
191 typeName === 'String' ||
192 typeName === 'Boolean' ||
193 typeName === 'Symbol'
194 ) {
195 return 'constant';
196 } else if (
197 typeName === 'Map' ||
198 typeName === 'WeakMap' ||
199 typeName === 'Set'
200 ) {
201 return 'collection';
202 } else if (typeName === 'RegExp') {
203 return 'regexp';
204 } else if (ref === undefined) {
205 return 'undefined';
206 } else if (ref === null) {
207 return 'null';
208 } else {
209 return null;
210 }
211}
212
213function isReadonlyProp(object, prop) {
214 if (
215 prop === 'arguments' ||
216 prop === 'caller' ||
217 prop === 'callee' ||
218 prop === 'name' ||
219 prop === 'length'
220 ) {
221 const typeName = getObjectType(object);
222 return (
223 typeName === 'Function' ||
224 typeName === 'AsyncFunction' ||
225 typeName === 'GeneratorFunction'
226 );
227 }
228
229 if (
230 prop === 'source' ||
231 prop === 'global' ||
232 prop === 'ignoreCase' ||
233 prop === 'multiline'
234 ) {
235 return getObjectType(object) === 'RegExp';
236 }
237
238 return false;
239}
240
241class ModuleMockerClass {
242 /**
243 * @see README.md
244 * @param global Global object of the test environment, used to create
245 * mocks
246 */
247 constructor(global) {
248 _defineProperty(this, '_environmentGlobal', void 0);
249
250 _defineProperty(this, '_mockState', void 0);
251
252 _defineProperty(this, '_mockConfigRegistry', void 0);
253
254 _defineProperty(this, '_spyState', void 0);
255
256 _defineProperty(this, '_invocationCallCounter', void 0);
257
258 _defineProperty(this, 'ModuleMocker', void 0);
259
260 this._environmentGlobal = global;
261 this._mockState = new WeakMap();
262 this._mockConfigRegistry = new WeakMap();
263 this._spyState = new Set();
264 this.ModuleMocker = ModuleMockerClass;
265 this._invocationCallCounter = 1;
266 }
267
268 _getSlots(object) {
269 if (!object) {
270 return [];
271 }
272
273 const slots = new Set();
274 const EnvObjectProto = this._environmentGlobal.Object.prototype;
275 const EnvFunctionProto = this._environmentGlobal.Function.prototype;
276 const EnvRegExpProto = this._environmentGlobal.RegExp.prototype; // Also check the builtins in the current context as they leak through
277 // core node modules.
278
279 const ObjectProto = Object.prototype;
280 const FunctionProto = Function.prototype;
281 const RegExpProto = RegExp.prototype; // Properties of Object.prototype, Function.prototype and RegExp.prototype
282 // are never reported as slots
283
284 while (
285 object != null &&
286 object !== EnvObjectProto &&
287 object !== EnvFunctionProto &&
288 object !== EnvRegExpProto &&
289 object !== ObjectProto &&
290 object !== FunctionProto &&
291 object !== RegExpProto
292 ) {
293 const ownNames = Object.getOwnPropertyNames(object);
294
295 for (let i = 0; i < ownNames.length; i++) {
296 const prop = ownNames[i];
297
298 if (!isReadonlyProp(object, prop)) {
299 const propDesc = Object.getOwnPropertyDescriptor(object, prop); // @ts-ignore Object.__esModule
300
301 if ((propDesc !== undefined && !propDesc.get) || object.__esModule) {
302 slots.add(prop);
303 }
304 }
305 }
306
307 object = Object.getPrototypeOf(object);
308 }
309
310 return Array.from(slots);
311 }
312
313 _ensureMockConfig(f) {
314 let config = this._mockConfigRegistry.get(f);
315
316 if (!config) {
317 config = this._defaultMockConfig();
318
319 this._mockConfigRegistry.set(f, config);
320 }
321
322 return config;
323 }
324
325 _ensureMockState(f) {
326 let state = this._mockState.get(f);
327
328 if (!state) {
329 state = this._defaultMockState();
330
331 this._mockState.set(f, state);
332 }
333
334 return state;
335 }
336
337 _defaultMockConfig() {
338 return {
339 mockImpl: undefined,
340 mockName: 'jest.fn()',
341 specificMockImpls: [],
342 specificReturnValues: []
343 };
344 }
345
346 _defaultMockState() {
347 return {
348 calls: [],
349 instances: [],
350 invocationCallOrder: [],
351 results: []
352 };
353 }
354
355 _makeComponent(metadata, restore) {
356 if (metadata.type === 'object') {
357 return new this._environmentGlobal.Object();
358 } else if (metadata.type === 'array') {
359 return new this._environmentGlobal.Array();
360 } else if (metadata.type === 'regexp') {
361 return new this._environmentGlobal.RegExp('');
362 } else if (
363 metadata.type === 'constant' ||
364 metadata.type === 'collection' ||
365 metadata.type === 'null' ||
366 metadata.type === 'undefined'
367 ) {
368 return metadata.value;
369 } else if (metadata.type === 'function') {
370 const prototype =
371 (metadata.members &&
372 metadata.members.prototype &&
373 metadata.members.prototype.members) ||
374 {};
375
376 const prototypeSlots = this._getSlots(prototype);
377
378 const mocker = this;
379 const mockConstructor = matchArity(function (...args) {
380 const mockState = mocker._ensureMockState(f);
381
382 const mockConfig = mocker._ensureMockConfig(f);
383
384 mockState.instances.push(this);
385 mockState.calls.push(args); // Create and record an "incomplete" mock result immediately upon
386 // calling rather than waiting for the mock to return. This avoids
387 // issues caused by recursion where results can be recorded in the
388 // wrong order.
389
390 const mockResult = {
391 type: 'incomplete',
392 value: undefined
393 };
394 mockState.results.push(mockResult);
395 mockState.invocationCallOrder.push(mocker._invocationCallCounter++); // Will be set to the return value of the mock if an error is not thrown
396
397 let finalReturnValue; // Will be set to the error that is thrown by the mock (if it throws)
398
399 let thrownError; // Will be set to true if the mock throws an error. The presence of a
400 // value in `thrownError` is not a 100% reliable indicator because a
401 // function could throw a value of undefined.
402
403 let callDidThrowError = false;
404
405 try {
406 // The bulk of the implementation is wrapped in an immediately
407 // executed arrow function so the return value of the mock function
408 // can be easily captured and recorded, despite the many separate
409 // return points within the logic.
410 finalReturnValue = (() => {
411 if (this instanceof f) {
412 // This is probably being called as a constructor
413 prototypeSlots.forEach(slot => {
414 // Copy prototype methods to the instance to make
415 // it easier to interact with mock instance call and
416 // return values
417 if (prototype[slot].type === 'function') {
418 // @ts-ignore no index signature
419 const protoImpl = this[slot]; // @ts-ignore no index signature
420
421 this[slot] = mocker.generateFromMetadata(prototype[slot]); // @ts-ignore no index signature
422
423 this[slot]._protoImpl = protoImpl;
424 }
425 }); // Run the mock constructor implementation
426
427 const mockImpl = mockConfig.specificMockImpls.length
428 ? mockConfig.specificMockImpls.shift()
429 : mockConfig.mockImpl;
430 return mockImpl && mockImpl.apply(this, arguments);
431 } // If mockImplementationOnce()/mockImplementation() is last set,
432 // implementation use the mock
433
434 let specificMockImpl = mockConfig.specificMockImpls.shift();
435
436 if (specificMockImpl === undefined) {
437 specificMockImpl = mockConfig.mockImpl;
438 }
439
440 if (specificMockImpl) {
441 return specificMockImpl.apply(this, arguments);
442 } // Otherwise use prototype implementation
443
444 if (f._protoImpl) {
445 return f._protoImpl.apply(this, arguments);
446 }
447
448 return undefined;
449 })();
450 } catch (error) {
451 // Store the thrown error so we can record it, then re-throw it.
452 thrownError = error;
453 callDidThrowError = true;
454 throw error;
455 } finally {
456 // Record the result of the function.
457 // NOTE: Intentionally NOT pushing/indexing into the array of mock
458 // results here to avoid corrupting results data if mockClear()
459 // is called during the execution of the mock.
460 mockResult.type = callDidThrowError ? 'throw' : 'return';
461 mockResult.value = callDidThrowError ? thrownError : finalReturnValue;
462 }
463
464 return finalReturnValue;
465 }, metadata.length || 0);
466
467 const f = this._createMockFunction(metadata, mockConstructor);
468
469 f._isMockFunction = true;
470
471 f.getMockImplementation = () => this._ensureMockConfig(f).mockImpl;
472
473 if (typeof restore === 'function') {
474 this._spyState.add(restore);
475 }
476
477 this._mockState.set(f, this._defaultMockState());
478
479 this._mockConfigRegistry.set(f, this._defaultMockConfig());
480
481 Object.defineProperty(f, 'mock', {
482 configurable: false,
483 enumerable: true,
484 get: () => this._ensureMockState(f),
485 set: val => this._mockState.set(f, val)
486 });
487
488 f.mockClear = () => {
489 this._mockState.delete(f);
490
491 return f;
492 };
493
494 f.mockReset = () => {
495 f.mockClear();
496
497 this._mockConfigRegistry.delete(f);
498
499 return f;
500 };
501
502 f.mockRestore = () => {
503 f.mockReset();
504 return restore ? restore() : undefined;
505 };
506
507 f.mockReturnValueOnce = (
508 value // next function call will return this value or default return value
509 ) => f.mockImplementationOnce(() => value);
510
511 f.mockResolvedValueOnce = value =>
512 f.mockImplementationOnce(() => Promise.resolve(value));
513
514 f.mockRejectedValueOnce = value =>
515 f.mockImplementationOnce(() => Promise.reject(value));
516
517 f.mockReturnValue = (
518 value // next function call will return specified return value or this one
519 ) => f.mockImplementation(() => value);
520
521 f.mockResolvedValue = value =>
522 f.mockImplementation(() => Promise.resolve(value));
523
524 f.mockRejectedValue = value =>
525 f.mockImplementation(() => Promise.reject(value));
526
527 f.mockImplementationOnce = fn => {
528 // next function call will use this mock implementation return value
529 // or default mock implementation return value
530 const mockConfig = this._ensureMockConfig(f);
531
532 mockConfig.specificMockImpls.push(fn);
533 return f;
534 };
535
536 f.mockImplementation = fn => {
537 // next function call will use mock implementation return value
538 const mockConfig = this._ensureMockConfig(f);
539
540 mockConfig.mockImpl = fn;
541 return f;
542 };
543
544 f.mockReturnThis = () =>
545 f.mockImplementation(function () {
546 return this;
547 });
548
549 f.mockName = name => {
550 if (name) {
551 const mockConfig = this._ensureMockConfig(f);
552
553 mockConfig.mockName = name;
554 }
555
556 return f;
557 };
558
559 f.getMockName = () => {
560 const mockConfig = this._ensureMockConfig(f);
561
562 return mockConfig.mockName || 'jest.fn()';
563 };
564
565 if (metadata.mockImpl) {
566 f.mockImplementation(metadata.mockImpl);
567 }
568
569 return f;
570 } else {
571 const unknownType = metadata.type || 'undefined type';
572 throw new Error('Unrecognized type ' + unknownType);
573 }
574 }
575
576 _createMockFunction(metadata, mockConstructor) {
577 let name = metadata.name;
578
579 if (!name) {
580 return mockConstructor;
581 } // Preserve `name` property of mocked function.
582
583 const boundFunctionPrefix = 'bound ';
584 let bindCall = ''; // if-do-while for perf reasons. The common case is for the if to fail.
585
586 if (name && name.startsWith(boundFunctionPrefix)) {
587 do {
588 name = name.substring(boundFunctionPrefix.length); // Call bind() just to alter the function name.
589
590 bindCall = '.bind(null)';
591 } while (name && name.startsWith(boundFunctionPrefix));
592 } // Special case functions named `mockConstructor` to guard for infinite
593 // loops.
594
595 if (name === MOCK_CONSTRUCTOR_NAME) {
596 return mockConstructor;
597 }
598
599 if (
600 // It's a syntax error to define functions with a reserved keyword
601 // as name.
602 RESERVED_KEYWORDS.has(name) || // It's also a syntax error to define functions with a name that starts with a number
603 /^\d/.test(name)
604 ) {
605 name = '$' + name;
606 } // It's also a syntax error to define a function with a reserved character
607 // as part of it's name.
608
609 if (FUNCTION_NAME_RESERVED_PATTERN.test(name)) {
610 name = name.replace(FUNCTION_NAME_RESERVED_REPLACE, '$');
611 }
612
613 const body =
614 'return function ' +
615 name +
616 '() {' +
617 'return ' +
618 MOCK_CONSTRUCTOR_NAME +
619 '.apply(this,arguments);' +
620 '}' +
621 bindCall;
622 const createConstructor = new this._environmentGlobal.Function(
623 MOCK_CONSTRUCTOR_NAME,
624 body
625 );
626 return createConstructor(mockConstructor);
627 }
628
629 _generateMock(metadata, callbacks, refs) {
630 // metadata not compatible but it's the same type, maybe problem with
631 // overloading of _makeComponent and not _generateMock?
632 // @ts-ignore
633 const mock = this._makeComponent(metadata);
634
635 if (metadata.refID != null) {
636 refs[metadata.refID] = mock;
637 }
638
639 this._getSlots(metadata.members).forEach(slot => {
640 const slotMetadata = (metadata.members && metadata.members[slot]) || {};
641
642 if (slotMetadata.ref != null) {
643 callbacks.push(
644 (function (ref) {
645 return () => (mock[slot] = refs[ref]);
646 })(slotMetadata.ref)
647 );
648 } else {
649 mock[slot] = this._generateMock(slotMetadata, callbacks, refs);
650 }
651 });
652
653 if (
654 metadata.type !== 'undefined' &&
655 metadata.type !== 'null' &&
656 mock.prototype &&
657 typeof mock.prototype === 'object'
658 ) {
659 mock.prototype.constructor = mock;
660 }
661
662 return mock;
663 }
664 /**
665 * @see README.md
666 * @param _metadata Metadata for the mock in the schema returned by the
667 * getMetadata method of this module.
668 */
669
670 generateFromMetadata(_metadata) {
671 const callbacks = [];
672 const refs = {};
673
674 const mock = this._generateMock(_metadata, callbacks, refs);
675
676 callbacks.forEach(setter => setter());
677 return mock;
678 }
679 /**
680 * @see README.md
681 * @param component The component for which to retrieve metadata.
682 */
683
684 getMetadata(component, _refs) {
685 const refs = _refs || new Map();
686 const ref = refs.get(component);
687
688 if (ref != null) {
689 return {
690 ref
691 };
692 }
693
694 const type = getType(component);
695
696 if (!type) {
697 return null;
698 }
699
700 const metadata = {
701 type
702 };
703
704 if (
705 type === 'constant' ||
706 type === 'collection' ||
707 type === 'undefined' ||
708 type === 'null'
709 ) {
710 metadata.value = component;
711 return metadata;
712 } else if (type === 'function') {
713 // @ts-ignore this is a function so it has a name
714 metadata.name = component.name; // @ts-ignore may be a mock
715
716 if (component._isMockFunction === true) {
717 // @ts-ignore may be a mock
718 metadata.mockImpl = component.getMockImplementation();
719 }
720 }
721
722 metadata.refID = refs.size;
723 refs.set(component, metadata.refID);
724 let members = null; // Leave arrays alone
725
726 if (type !== 'array') {
727 this._getSlots(component).forEach(slot => {
728 if (
729 type === 'function' && // @ts-ignore may be a mock
730 component._isMockFunction === true &&
731 slot.match(/^mock/)
732 ) {
733 return;
734 } // @ts-ignore no index signature
735
736 const slotMetadata = this.getMetadata(component[slot], refs);
737
738 if (slotMetadata) {
739 if (!members) {
740 members = {};
741 }
742
743 members[slot] = slotMetadata;
744 }
745 });
746 }
747
748 if (members) {
749 metadata.members = members;
750 }
751
752 return metadata;
753 }
754
755 isMockFunction(fn) {
756 return !!fn && fn._isMockFunction === true;
757 }
758
759 fn(implementation) {
760 const length = implementation ? implementation.length : 0;
761
762 const fn = this._makeComponent({
763 length,
764 type: 'function'
765 });
766
767 if (implementation) {
768 fn.mockImplementation(implementation);
769 }
770
771 return fn;
772 }
773
774 spyOn(object, methodName, accessType) {
775 if (accessType) {
776 return this._spyOnProperty(object, methodName, accessType);
777 }
778
779 if (typeof object !== 'object' && typeof object !== 'function') {
780 throw new Error(
781 'Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given'
782 );
783 }
784
785 const original = object[methodName];
786
787 if (!this.isMockFunction(original)) {
788 if (typeof original !== 'function') {
789 throw new Error(
790 'Cannot spy the ' +
791 methodName +
792 ' property because it is not a function; ' +
793 this._typeOf(original) +
794 ' given instead'
795 );
796 }
797
798 const isMethodOwner = object.hasOwnProperty(methodName); // @ts-ignore overriding original method with a Mock
799
800 object[methodName] = this._makeComponent(
801 {
802 type: 'function'
803 },
804 () => {
805 if (isMethodOwner) {
806 object[methodName] = original;
807 } else {
808 delete object[methodName];
809 }
810 }
811 ); // @ts-ignore original method is now a Mock
812
813 object[methodName].mockImplementation(function () {
814 return original.apply(this, arguments);
815 });
816 }
817
818 return object[methodName];
819 }
820
821 _spyOnProperty(obj, propertyName, accessType = 'get') {
822 if (typeof obj !== 'object' && typeof obj !== 'function') {
823 throw new Error(
824 'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given'
825 );
826 }
827
828 if (!obj) {
829 throw new Error(
830 'spyOn could not find an object to spy upon for ' + propertyName + ''
831 );
832 }
833
834 if (!propertyName) {
835 throw new Error('No property name supplied');
836 }
837
838 let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
839 let proto = Object.getPrototypeOf(obj);
840
841 while (!descriptor && proto !== null) {
842 descriptor = Object.getOwnPropertyDescriptor(proto, propertyName);
843 proto = Object.getPrototypeOf(proto);
844 }
845
846 if (!descriptor) {
847 throw new Error(propertyName + ' property does not exist');
848 }
849
850 if (!descriptor.configurable) {
851 throw new Error(propertyName + ' is not declared configurable');
852 }
853
854 if (!descriptor[accessType]) {
855 throw new Error(
856 'Property ' + propertyName + ' does not have access type ' + accessType
857 );
858 }
859
860 const original = descriptor[accessType];
861
862 if (!this.isMockFunction(original)) {
863 if (typeof original !== 'function') {
864 throw new Error(
865 'Cannot spy the ' +
866 propertyName +
867 ' property because it is not a function; ' +
868 this._typeOf(original) +
869 ' given instead'
870 );
871 } // @ts-ignore: mock is assignable
872
873 descriptor[accessType] = this._makeComponent(
874 {
875 type: 'function'
876 },
877 () => {
878 // @ts-ignore: mock is assignable
879 descriptor[accessType] = original;
880 Object.defineProperty(obj, propertyName, descriptor);
881 }
882 );
883 descriptor[accessType].mockImplementation(function () {
884 // @ts-ignore
885 return original.apply(this, arguments);
886 });
887 }
888
889 Object.defineProperty(obj, propertyName, descriptor);
890 return descriptor[accessType];
891 }
892
893 clearAllMocks() {
894 this._mockState = new WeakMap();
895 }
896
897 resetAllMocks() {
898 this._mockConfigRegistry = new WeakMap();
899 this._mockState = new WeakMap();
900 }
901
902 restoreAllMocks() {
903 this._spyState.forEach(restore => restore());
904
905 this._spyState = new Set();
906 }
907
908 _typeOf(value) {
909 return value == null ? '' + value : typeof value;
910 }
911}
912/* eslint-disable-next-line no-redeclare */
913
914const JestMock = new ModuleMockerClass(global);
915module.exports = JestMock;