Skip to content

Commit 5ce2bb7

Browse files
authored
fix: reuse spy when calling spyOn (#47)
* fix: reuse spy when calling spyOn * test: remove only
1 parent 4ba8197 commit 5ce2bb7

File tree

3 files changed

+47
-14
lines changed

3 files changed

+47
-14
lines changed

src/internal.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ export function createInternalSpy<A extends any[], R>(
138138
export function populateSpy<A extends any[], R>(spy: SpyInternal<A, R>) {
139139
const I = getInternalState(spy)
140140

141+
// already populated
142+
if ('returns' in spy) {
143+
return
144+
}
145+
141146
define(spy, 'returns', {
142147
get: () => I.results.map(([, r]) => r),
143148
})

src/spyOn.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
populateSpy,
44
spies,
55
SpyImpl,
6+
SpyInternal,
67
SpyInternalImpl,
78
} from './internal'
89
import { assert, define, defineValue, isType } from './utils'
@@ -47,7 +48,7 @@ export function internalSpyOn<T, K extends string & keyof T>(
4748

4849
let [accessName, accessType] = ((): [
4950
string | symbol | number,
50-
'value' | 'get' | 'set'
51+
'value' | 'get' | 'set',
5152
] => {
5253
if (!isType('object', methodName)) {
5354
return [methodName, 'value']
@@ -97,12 +98,6 @@ export function internalSpyOn<T, K extends string & keyof T>(
9798
origin = obj[accessName as keyof T] as unknown as Procedure
9899
}
99100

100-
if (!mock) mock = origin
101-
102-
let fn = createInternalSpy(mock)
103-
if (accessType === 'value') {
104-
prototype(fn, origin)
105-
}
106101
let reassign = (cb: any) => {
107102
let { value, ...desc } = originalDescriptor || {
108103
configurable: true,
@@ -118,13 +113,26 @@ export function internalSpyOn<T, K extends string & keyof T>(
118113
originalDescriptor
119114
? define(obj, accessName, originalDescriptor)
120115
: reassign(origin)
121-
const state = fn[S]
122-
defineValue(state, 'restore', restore)
123-
defineValue(state, 'getOriginal', () => (ssr ? origin() : origin))
124-
defineValue(state, 'willCall', (newCb: Procedure) => {
125-
state.impl = newCb
126-
return fn
127-
})
116+
117+
if (!mock) mock = origin
118+
119+
let fn: SpyInternal
120+
if (origin && S in origin) {
121+
fn = origin as SpyInternal
122+
} else {
123+
fn = createInternalSpy(mock)
124+
if (accessType === 'value') {
125+
prototype(fn, origin)
126+
}
127+
128+
const state = fn[S]
129+
defineValue(state, 'restore', restore)
130+
defineValue(state, 'getOriginal', () => (ssr ? origin() : origin))
131+
defineValue(state, 'willCall', (newCb: Procedure) => {
132+
state.impl = newCb
133+
return fn
134+
})
135+
}
128136

129137
reassign(
130138
ssr

test/index.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,23 @@ test('next in a row', () => {
755755
expect(cb()).toBe(3)
756756
expect(cb()).toBe(undefined)
757757
})
758+
759+
test('spying twice and unspying restores original method', () => {
760+
const obj = {
761+
method: () => 1,
762+
}
763+
const spy1 = spyOn(obj, 'method').willCall(() => 2)
764+
expect(obj.method()).toBe(2)
765+
766+
const spy2 = spyOn(obj, 'method')
767+
768+
expect(spy1).toBe(spy2)
769+
expect(obj.method()).toBe(2)
770+
771+
spy2.willCall(() => 3)
772+
773+
expect(obj.method()).toBe(3)
774+
775+
spy2.restore()
776+
expect(obj.method).not.toBe(spy1)
777+
})

0 commit comments

Comments
 (0)