Last active
April 26, 2022 01:43
-
-
Save sidouglas/a2e34d4715f5d34b8b4207fea5e3bbdc to your computer and use it in GitHub Desktop.
Jest spy on everything
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default (interrogate, mockImplementations = {}) => { | |
const spies = {} | |
interrogate.prototype && Object.getOwnPropertyNames(interrogate.prototype).forEach((name) => { | |
if (name !== 'constructor') { | |
spies[`${name}Spy`] = createSpy(interrogate.prototype, mockImplementations, name) | |
} | |
// AFAIK, you can't spy on a constructor | |
// top answer is here: https://stackoverflow.com/a/48486214/1090606 | |
// For now, mock the entire module to assert that the constructor was called correctly. | |
}) | |
Object.getOwnPropertyNames(interrogate) | |
.filter(prop => typeof interrogate[prop] === 'function') | |
.forEach((name) => { | |
spies[`${name}Spy`] = createSpy(interrogate, mockImplementations, name) | |
}) | |
return spies | |
} | |
/** | |
* @param object | |
* @param {Object} mockImplementations | |
* @param {string} name | |
* @returns {jest.SpyInstance<*, []>} | |
*/ | |
function createSpy (object, mockImplementations, name) { | |
if (name.startsWith('__')) { | |
return | |
} | |
try { | |
const descriptor = getPropertyDescriptor(object, name) || { get: null } | |
const accessType = descriptor.get | |
? 'get' | |
: descriptor.set | |
? 'set' | |
: undefined | |
const mockFunction = mockImplementations[name] | |
return (typeof mockFunction === 'function') | |
? jest.spyOn(object, name, ...(accessType ? [accessType] : [])).mockImplementation(() => mockFunction()) | |
: jest.spyOn(object, name, ...(accessType ? [accessType] : [])) | |
} catch (error) { | |
/* can't spy on everything */ | |
console.log('spyOnAll failed for', name, error) | |
} | |
} | |
/** | |
* @param obj | |
* @param prop | |
* @returns {PropertyDescriptor} | |
*/ | |
function getPropertyDescriptor (obj, prop) { | |
let desc | |
do { | |
desc = Object.getOwnPropertyDescriptor(obj, prop) | |
} while (!desc && (obj = Object.getPrototypeOf(obj))) | |
return desc | |
} | |
// example follows | |
class A { | |
constructor(a){ | |
this._a = a | |
} | |
get a (){ | |
return this._a | |
} | |
someThing(){ | |
// | |
} | |
anotherThing(){ | |
return this._a + 'anotherThing' | |
} | |
} | |
const spies = spyOnAll(A, {someThing: () => 'does something else'}) | |
/* | |
spies.aSpy -> spy on A.a and will behave like a normal getter for A.a | |
spies.someThingSpy -> spy on A.someThing but has its own implemention ( will return 'does something else' ) | |
spies.anotherThingSpy -> spy on A.anotherThing and will call through as per normal | |
spies.constructorSpy -> spy on constructor -> have not tested this but would suggest using https://stackoverflow.com/a/65975355/1090606 | |
*/ | |
Useful work arounds: https://github.com/facebook/jest/issues/6914#issuecomment-714848220 | |
1. mock the module | |
2. spy on the methods | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment