Skip to content

Instantly share code, notes, and snippets.

@sidouglas
Last active April 26, 2022 01:43
Show Gist options
  • Save sidouglas/a2e34d4715f5d34b8b4207fea5e3bbdc to your computer and use it in GitHub Desktop.
Save sidouglas/a2e34d4715f5d34b8b4207fea5e3bbdc to your computer and use it in GitHub Desktop.
Jest spy on everything
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