Skip to content

Commit 85e1738

Browse files
authored
Merge pull request #93 from stbestichhh/84-update-spy
Spy standalone functions
2 parents 01066a4 + 5051e35 commit 85e1738

File tree

9 files changed

+90
-24
lines changed

9 files changed

+90
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99
## Added:
10-
* New assertions. See [assertions API](README.md#assertions-api)
10+
* New assertions. See [assertions API](README.md#assertions-api)
11+
* New spy method `call(...args: any[])`, which allows to run function with spying it
1112

1213
## Fix:
1314
* Fixed error on wrong keybinding pressed

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ $ npx stest
142142
| `toBeTypeOf(type: any)` | Check if actual is type of expected. Example: `assertThat('a').toBeTypeOf('string')`, `assertThat(TypeError).toBeTypeOf(Error)` |
143143
| `toHaveProperty(property: any)` | Check if actual has expected property |
144144
| `toHavePropertyEqual(property: any, value: any)` | Check if actual has expected property which is equal to value |
145-
| `toHaveProperties(properties: any[])` | Check if actual has all of expected properties |
145+
| `toHaveAllProperties(properties: any[])` | Check if actual has all of expected properties |
146146
| `toHaveAnyProperties(properties: any[])` | Check if actual has any of expected properties |
147147
| `toThrow(expectedError?: ErrorConstructor, ...args: any[])` | Check if actual throw an error or expected error. Also you can provide arguments for actual function |
148148
| `toNotThrow(expectedError?: ErrorConstructor, ...args: any[])` | Check if actual do not throw an error or do not throw an error provided to as 'expectedError' param. Also you can provide arguments for actual function |
@@ -185,10 +185,27 @@ $ npx stest
185185
#### Spy API
186186

187187
Create a new spy by `spyOn(instance: any, methodName: string)` function:
188-
```TypeScript
189-
const example = new ExampleClass();
190-
const greetSpy = spyOn(example, 'greet');
191-
```
188+
189+
* Spy methods:
190+
```TypeScript
191+
const example = new ExampleClass();
192+
const greetSpy = spyOn(example, 'greet');
193+
194+
// Call method
195+
example.greet('Hello, World!');
196+
```
197+
* Spy functions
198+
```TypeScript
199+
const func = () => { return 'Hello' };
200+
const greetSpy = spyOn(func);
201+
202+
// Call function
203+
greetSpy.call('Hello, World!');
204+
greetSpy.getCallCount(); // 1
205+
206+
// Spy won't work for calling function directly
207+
func('Hello, World!'); // still 1
208+
```
192209

193210
| Class | Method | Description |
194211
|------------------------------------------|---------------------------------------|-----------------------------------------------------------------------------|
@@ -200,6 +217,7 @@ const greetSpy = spyOn(example, 'greet');
200217
| | `getThrownErrors(callIndex?: number)` | Returns an array with with all thrown errors |
201218
| | `wasCalled(amount?: number)` | Returns true if method was called at least once or more than `amount` times |
202219
| | `wasCalledWith(...args: any[])` | Returns true if method was called with specified arguments at least once |
220+
| | `call(...args: any[])` | Calls spied function |
203221

204222
#### Snapshots API
205223

example/assertions/property_assertions.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@ class PropertyTests {
66
@Case('Should have property')
77
checkHaveProperty() {
88
const obj = { name: 'John', age: 30 };
9-
assertThat(obj).toHaveProperty('name');
9+
assertThat(obj)
10+
.toHaveProperty('name')
11+
.toHaveAllProperties(['name', 'age'])
12+
.toHaveAnyProperties(['a', 'age']);
1013
}
1114

1215
@Case('Should satisfy predicate')
13-
checkSatisfyPredicate() {
16+
checkLength() {
1417
assertThat([1, 2, 3, 4, 5]).toHaveLength(5);
1518
}
19+
20+
@Case()
21+
checkPropertyEqual() {
22+
const obj = { name: 'John', age: 30 };
23+
assertThat(obj).toHavePropertyEqual('age', 30);
24+
}
1625
}

example/spy/spy_example.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,26 @@ class ExampleClass {
66
}
77
}
88

9+
const greet = (name: string) => {
10+
return `Hello, ${name}!`;
11+
}
12+
913
@Test('Spying Example')
1014
class SpyingTestSuite {
1115

1216
@Case('should track function calls')
17+
testFunctionSpying() {
18+
const greetSpy = spyOn(greet);
19+
20+
greetSpy.call('Alice');
21+
greetSpy.call('Bob');
22+
23+
assertThat(greetSpy.getCallCount()).toEqual(2);
24+
assertThat(greetSpy.wasCalledWith('Alice')).toBeTruthy();
25+
assertThat(greetSpy.getCallArgs(1)[0]).toEqual('Bob');
26+
}
27+
28+
@Case('should track method calls')
1329
testFunctionSpying() {
1430
const example = new ExampleClass();
1531
const greetSpy = spyOn(example, 'greet');
@@ -22,7 +38,7 @@ class SpyingTestSuite {
2238
assertThat(greetSpy.getCallArgs(1)[0]).toEqual('Bob');
2339
}
2440

25-
@Case('should track the order of function calls')
41+
@Case('should track the order of method calls')
2642
testFunctionCallOrder() {
2743
const example = new ExampleClass();
2844
const greetSpy = spyOn(example, 'greet');
@@ -36,7 +52,7 @@ class SpyingTestSuite {
3652
assertThat(callOrder[1]).toEqual(2);
3753
}
3854

39-
@Case('should track errors thrown by the function')
55+
@Case('should track errors thrown by the method')
4056
testFunctionThrowsError() {
4157
const example = new ExampleClass();
4258
const mock = new Mock(example);

lib/assertions/assert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export function assertThat(actual: any): IAssertion {
236236
return this;
237237
},
238238

239-
toHaveProperties(properties: any[]): IAssertion {
239+
toHaveAllProperties(properties: any[]): IAssertion {
240240
for (const prop of properties) {
241241
if (!Object.hasOwn(actual, prop)) {
242242
throw new AssertionException(

lib/interfaces/IAssertion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ interface ITypeAssertion {
4545

4646
interface IPropertyAssertion {
4747
toHaveProperty(property: any): IAssertion;
48-
toHaveProperties(properties: any[]): IAssertion;
48+
toHaveAllProperties(properties: any[]): IAssertion;
4949
toHaveAnyProperties(properties: any[]): IAssertion;
5050
toHavePropertyEqual(property: any, value: any): IAssertion;
5151
toHaveLength(expected: number): IAssertion;

lib/spy/spy.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { AnyFunction } from '../types';
22

33
export class Spy {
44
private originalFunction: AnyFunction;
5-
private context: any;
65
private callCount: number = 0;
76
private callArgs: any[][] = [];
87
private callResults: any[] = [];
98
private callOrder: number[] = [];
109
private thrownExceptions: any[] = [];
10+
private readonly context: any;
1111

1212
constructor(originalFunction: AnyFunction, context: any = null) {
1313
this.originalFunction = originalFunction;
@@ -61,6 +61,10 @@ export class Spy {
6161
return this.callArgs.some((callArgs) => this.compareArgs(callArgs, args));
6262
}
6363

64+
public call(...args: any[]) {
65+
this.spyWrapper(...args);
66+
}
67+
6468
private compareArgs(args1: any[], args2: any[]) {
6569
return JSON.stringify(args1) === JSON.stringify(args2);
6670
}

lib/spy/spyOn.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
import { Spy } from './spy';
22
import { MockRegistry } from '../mocking';
33

4-
export function spyOn(object: any, methodName: string) {
5-
const originalMethod = object[methodName];
6-
const spy = new Spy(originalMethod, object);
4+
export function spyOn(object: any, methodName?: string) {
5+
if (
6+
methodName === '__proto__' ||
7+
methodName === 'constructor' ||
8+
methodName === 'prototype'
9+
) {
10+
throw new Error('Invalid method name');
11+
}
12+
const isFunctionSpy = typeof object === 'function' && !methodName;
713

8-
object[methodName] = spy.spyFn;
14+
const originalMethod = isFunctionSpy ? object : object[methodName!];
15+
const spy = new Spy(originalMethod, isFunctionSpy ? null : object);
916

10-
MockRegistry.registerMock({
11-
restoreAll: () => {
12-
object[methodName] = originalMethod;
13-
},
14-
});
17+
if (isFunctionSpy) {
18+
object = spy.spyFn;
19+
MockRegistry.registerMock({
20+
restoreAll: () => {
21+
object = originalMethod;
22+
},
23+
});
24+
} else {
25+
object[methodName!] = spy.spyFn;
26+
MockRegistry.registerMock({
27+
restoreAll: () => {
28+
object[methodName!] = originalMethod;
29+
},
30+
});
31+
}
1532

1633
return spy;
1734
}

stutil.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface ITypeAssertion {
4646

4747
interface IPropertyAssertion {
4848
toHaveProperty(property: any): IAssertion;
49-
toHaveProperties(properties: any[]): IAssertion;
49+
toHaveAllProperties(properties: any[]): IAssertion;
5050
toHaveAnyProperties(properties: any[]): IAssertion;
5151
toHavePropertyEqual(property: any, value: any): IAssertion;
5252
toHaveLength(expected: number): IAssertion;
@@ -154,9 +154,10 @@ class Spy {
154154
public getThrownErrors(callIndex?: number): any[];
155155
public wasCalled(): boolean;
156156
public wasCalledWith(...args: any[]): boolean;
157+
public call(...args: any[]): void;
157158
}
158159

159-
export function spyOn(object: any, methodName: string): Spy;
160+
export function spyOn(object: any, methodName?: string): Spy;
160161

161162
// Snapshot
162163
export function shot(name: string, data: any): void;

0 commit comments

Comments
 (0)