Skip to content

Commit

Permalink
improve and test overriding loaded object modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ChALkeR committed Jul 17, 2024
1 parent 989fdc1 commit 93b612a
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
41 changes: 41 additions & 0 deletions __test__/jest/jest.mock.types.override.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const number = require('./fixtures/number.cjs')
const object = require('./fixtures/object.cjs')
const classobject = require('./fixtures/classobject.cjs')
const subclassobject = require('./fixtures/subclassobject.cjs')

test('number', () => {
jest.mock('./fixtures/number.cjs')
expect(number).toBe(42)
})

test('object', () => {
jest.mock('./fixtures/object.cjs')
expect(object.value).toBe(20)
expect(object.hello).toBe('world')
expect(object.call()).toBe(undefined)
expect(() => object.none()).toThrow()
expect(object.stringobj).toBeInstanceOf(String)
expect(`${object.stringobj}`).toBe('hello')
expect(object.arr).toBeInstanceOf(Array)
// jest is weird for the below, but let's follow that
expect(object.arr.length).toBe(0)
})

test('classobject', () => {
jest.mock('./fixtures/classobject.cjs')
expect(classobject.something).toBe('fun')
expect(classobject.bar()).toBe(undefined)
expect(() => classobject.not()).toThrow()
})

test('subclassobject', () => {
jest.mock('./fixtures/subclassobject.cjs')
expect(subclassobject.hi()).toBe(undefined)
expect(subclassobject.why()).toBe(undefined)
expect(subclassobject.overridden()).toBe(undefined)
expect(() => subclassobject.foo()).toThrow()
expect(subclassobject.two).toBe(2)
expect(subclassobject.one).toBe(1)
expect(subclassobject.common).toBe('high')
expect(Object.getPrototypeOf(subclassobject)).toBe(Object.prototype) // flattened!
})
11 changes: 9 additions & 2 deletions src/jest.mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ const isObject = (obj) => [Object.prototype, null].includes(Object.getPrototypeO
function override(resolved, lax = false) {
const value = mapMocks.get(resolved)
const current = mapActual.get(resolved)
assert(isObject(current), 'Modules that export a default non-object can not be mocked')
if (current === value) return
assert(isObject(value), 'Overriding loaded or internal modules is possible with objects only')
mapActual.set(resolved, { ...current })
const clone = { ...current }
Object.setPrototypeOf(clone, Object.getPrototypeOf(current))
mapActual.set(resolved, clone)
for (const key of Object.keys(current)) {
try {
delete current[key]
Expand All @@ -58,6 +60,8 @@ function override(resolved, lax = false) {
const access = { configurable: true, enumerable: true, writable: true }
const definitions = Object.fromEntries(filtered.map(([k, value]) => [k, { value, ...access }]))
Object.defineProperties(current, definitions)
const proto = Object.getPrototypeOf(value)
if (Object.getPrototypeOf(current) !== proto) Object.setPrototypeOf(current, proto)
if (!lax) assert.deepEqual({ ...current }, value)
}

Expand Down Expand Up @@ -94,13 +98,16 @@ function mockCloneItem(obj, cache) {
let modified = stack.length > 1
for (const level of stack) {
for (const [name, desc] of Object.entries(Object.getOwnPropertyDescriptors(level))) {
if (name === 'constructor') continue

for (const key of ['get', 'set', 'value']) {
if (!desc[key]) continue
const orig = desc[key]
desc[key] = mockClone(desc[key], cache)
if (orig !== desc[key]) modified = true
}

desc.enumerable = desc.configurable = true
if (desc.value !== undefined || desc.get || desc.set) definitions.push([name, desc])
}
}
Expand Down

0 comments on commit 93b612a

Please sign in to comment.