0
# Spy Operations
1
2
Non-invasive spying and property replacement capabilities for observing and temporarily modifying existing objects without permanent changes.
3
4
## Capabilities
5
6
### Method Spying
7
8
Creates spies on existing object methods to track calls while preserving original behavior.
9
10
```typescript { .api }
11
/**
12
* Creates a spy on an object method
13
* @param object - Target object containing the method
14
* @param methodKey - Name of the method to spy on
15
* @returns MockInstance for the spied method
16
*/
17
function spyOn<T extends object, K extends MethodLikeKeys<T>>(
18
object: T,
19
methodKey: K
20
): Spied<Required<T>[K]>;
21
22
/**
23
* Creates a spy on a property accessor (getter or setter)
24
* @param object - Target object containing the property
25
* @param propertyKey - Name of the property to spy on
26
* @param accessType - Whether to spy on 'get' or 'set' accessor
27
* @returns MockInstance for the property accessor
28
*/
29
function spyOn<T extends object, K extends PropertyLikeKeys<T>, A extends 'get' | 'set'>(
30
object: T,
31
propertyKey: K,
32
accessType: A
33
): A extends 'get' ? SpiedGetter<Required<T>[K]> : SpiedSetter<Required<T>[K]>;
34
35
type MethodLikeKeys<T> = keyof {
36
[K in keyof T as Required<T>[K] extends FunctionLike ? K : never]: T[K];
37
};
38
39
type PropertyLikeKeys<T> = Exclude<keyof T, ConstructorLikeKeys<T> | MethodLikeKeys<T>>;
40
41
type Spied<T extends ClassLike | FunctionLike> = T extends ClassLike
42
? SpiedClass<T>
43
: T extends FunctionLike
44
? SpiedFunction<T>
45
: never;
46
47
type SpiedFunction<T extends FunctionLike = UnknownFunction> = MockInstance<(...args: Parameters<T>) => ReturnType<T>>;
48
type SpiedClass<T extends ClassLike = UnknownClass> = MockInstance<(...args: ConstructorParameters<T>) => InstanceType<T>>;
49
type SpiedGetter<T> = MockInstance<() => T>;
50
type SpiedSetter<T> = MockInstance<(arg: T) => void>;
51
```
52
53
**Usage Examples:**
54
55
```typescript
56
import { spyOn } from "jest-mock";
57
58
const calculator = {
59
add: (a: number, b: number) => a + b,
60
subtract: (a: number, b: number) => a - b,
61
history: [] as string[]
62
};
63
64
// Spy on a method
65
const addSpy = spyOn(calculator, 'add');
66
const result = calculator.add(2, 3);
67
console.log(result); // 5 (original behavior preserved)
68
console.log(addSpy.mock.calls); // [[2, 3]]
69
70
// Override spy behavior
71
addSpy.mockReturnValue(42);
72
console.log(calculator.add(1, 1)); // 42
73
74
// Restore original behavior
75
addSpy.mockRestore();
76
console.log(calculator.add(1, 1)); // 2
77
```
78
79
### Property Spying
80
81
Creates spies on property getters and setters.
82
83
```typescript { .api }
84
/**
85
* Spy on property getter
86
*/
87
spyOn(object, propertyKey, 'get'): SpiedGetter<PropertyType>;
88
89
/**
90
* Spy on property setter
91
*/
92
spyOn(object, propertyKey, 'set'): SpiedSetter<PropertyType>;
93
```
94
95
**Usage Examples:**
96
97
```typescript
98
import { spyOn } from "jest-mock";
99
100
const config = {
101
_apiUrl: 'https://api.example.com',
102
get apiUrl() { return this._apiUrl; },
103
set apiUrl(value: string) { this._apiUrl = value; }
104
};
105
106
// Spy on getter
107
const getterSpy = spyOn(config, 'apiUrl', 'get');
108
console.log(config.apiUrl); // 'https://api.example.com'
109
console.log(getterSpy.mock.calls); // [[]]
110
111
// Override getter
112
getterSpy.mockReturnValue('https://test.example.com');
113
console.log(config.apiUrl); // 'https://test.example.com'
114
115
// Spy on setter
116
const setterSpy = spyOn(config, 'apiUrl', 'set');
117
config.apiUrl = 'https://new.example.com';
118
console.log(setterSpy.mock.calls); // [['https://new.example.com']]
119
```
120
121
### Property Replacement
122
123
Temporarily replaces object properties with new values, providing restoration capabilities.
124
125
```typescript { .api }
126
/**
127
* Temporarily replaces an object property with a new value
128
* @param object - Target object containing the property
129
* @param propertyKey - Name of the property to replace
130
* @param value - New value for the property
131
* @returns Replaced instance with control methods
132
*/
133
function replaceProperty<T extends object, K extends keyof T>(
134
object: T,
135
propertyKey: K,
136
value: T[K]
137
): Replaced<T[K]>;
138
139
interface Replaced<T = unknown> {
140
/** Restore property to its original value */
141
restore(): void;
142
/** Change the property to a new value */
143
replaceValue(value: T): this;
144
}
145
```
146
147
**Usage Examples:**
148
149
```typescript
150
import { replaceProperty } from "jest-mock";
151
152
const config = {
153
apiUrl: 'https://api.example.com',
154
timeout: 5000,
155
retries: 3
156
};
157
158
// Replace a property
159
const replaced = replaceProperty(config, 'apiUrl', 'https://test.example.com');
160
console.log(config.apiUrl); // 'https://test.example.com'
161
162
// Change to another value
163
replaced.replaceValue('https://staging.example.com');
164
console.log(config.apiUrl); // 'https://staging.example.com'
165
166
// Restore original value
167
replaced.restore();
168
console.log(config.apiUrl); // 'https://api.example.com'
169
170
// Replace multiple properties
171
const timeoutReplaced = replaceProperty(config, 'timeout', 1000);
172
const retriesReplaced = replaceProperty(config, 'retries', 1);
173
174
// Cleanup
175
timeoutReplaced.restore();
176
retriesReplaced.restore();
177
```
178
179
### Class Constructor Spying
180
181
Spy on class constructors to track instantiation.
182
183
```typescript { .api }
184
class MyClass {
185
constructor(public value: string) {}
186
method() { return this.value; }
187
}
188
189
// Spy on constructor
190
const ConstructorSpy = spyOn(MyClass.prototype, 'constructor');
191
const instance = new MyClass('test');
192
console.log(ConstructorSpy.mock.calls); // [['test']]
193
```
194
195
## Error Handling
196
197
Spy operations validate targets and throw descriptive errors for invalid usage:
198
199
```typescript
200
// Throws TypeError: Cannot spy on a primitive value
201
spyOn('not an object', 'method');
202
203
// Throws Error: Property 'nonexistent' does not exist
204
spyOn(obj, 'nonexistent');
205
206
// Throws TypeError: Cannot spy on property because it is not a function
207
spyOn(obj, 'stringProperty');
208
209
// Throws Error: Property is not declared configurable
210
const nonConfigurable = {};
211
Object.defineProperty(nonConfigurable, 'prop', { configurable: false, value: 'test' });
212
replaceProperty(nonConfigurable, 'prop', 'new');
213
```
214
215
## Advanced Usage
216
217
### Spy Restoration Patterns
218
219
```typescript
220
import { spyOn } from "jest-mock";
221
222
// Individual restoration
223
const spy1 = spyOn(obj, 'method1');
224
const spy2 = spyOn(obj, 'method2');
225
spy1.mockRestore();
226
spy2.mockRestore();
227
228
// Using ModuleMocker for bulk restoration
229
import { ModuleMocker } from "jest-mock";
230
const mocker = new ModuleMocker(globalThis);
231
const spy3 = mocker.spyOn(obj, 'method3');
232
const spy4 = mocker.spyOn(obj, 'method4');
233
mocker.restoreAllMocks(); // Restores all spies created by this mocker
234
```
235
236
### Spy Chaining
237
238
```typescript
239
import { spyOn } from "jest-mock";
240
241
const obj = {
242
getValue: () => 42,
243
chainable: function() { return this; }
244
};
245
246
// Chain spy operations
247
spyOn(obj, 'getValue')
248
.mockReturnValue(100)
249
.mockName('getValue spy');
250
251
spyOn(obj, 'chainable')
252
.mockReturnThis()
253
.mockName('chainable spy');
254
```
255
256
## Types
257
258
```typescript { .api }
259
type UnknownFunction = (...args: Array<unknown>) => unknown;
260
type UnknownClass = new (...args: Array<unknown>) => unknown;
261
type FunctionLike = (...args: any) => any;
262
type ClassLike = new (...args: any) => any;
263
264
type ConstructorLikeKeys<T> = keyof {
265
[K in keyof T as Required<T>[K] extends ClassLike ? K : never]: T[K];
266
};
267
```