Skip to content

Commit 96f7322

Browse files
authored
fix: fix void spy regressions, and properly type andCallFake (ngneat#710)
* fix!: fix void spy regressions, and properly type andCallFake BREAKING CHANGE: CompatibleSpy<T> type checks it's methods and enables types on inherited jasmine.Spy<T> methods. Existing tests may break at build if mocked returns are the wrong types. * test: add additional tests for type checks
1 parent 70c3a23 commit 96f7322

File tree

3 files changed

+117
-2
lines changed

3 files changed

+117
-2
lines changed

projects/spectator/src/lib/mock.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FactoryProvider, Type, AbstractType } from '@angular/core';
33

44
type Writable<T> = { -readonly [P in keyof T]: T[P] };
55

6-
declare type UnknownFunction = (...args: unknown[]) => unknown;
6+
declare type UnknownFunction = (...args: any[]) => any;
77

88
/**
99
* @publicApi
@@ -19,7 +19,7 @@ export interface CompatibleSpy<F extends UnknownFunction = UnknownFunction> exte
1919
* By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
2020
* function.
2121
*/
22-
andCallFake(fn: UnknownFunction): this;
22+
andCallFake(fn: F): this;
2323

2424
/**
2525
* removes all recorded calls

projects/spectator/test/spy-object/person.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ export class Person {
66
return 'Hi!';
77
}
88

9+
public saySomething(something: string): string {
10+
return `You said: ${something}`;
11+
}
12+
13+
public voidMethod(): void {
14+
// Do nothing
15+
}
16+
17+
public voidMethodWithArguments(arg1: string, arg2: number): void {
18+
// Do nothing
19+
}
20+
921
public get age(): number {
1022
return 2019 - this.birthYear;
1123
}

projects/spectator/test/spy-object/spy-object.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,110 @@ describe('SpyObject', () => {
66
it('should mock all public methods', () => {
77
const person = createSpyObject(Person);
88

9+
// Check methods without arguments but returns
910
person.sayHi.andReturn('Bye!');
11+
12+
// Check method with different return types
13+
// These shouldn not compile, but will with `@ts-expect-error` applied
14+
// @ts-expect-error
15+
person.sayHi.andReturn(false);
16+
// @ts-expect-error
17+
person.sayHi.andReturn(100);
18+
// @ts-expect-error
19+
person.sayHi.andReturn({ prop: true });
20+
// @ts-expect-error
21+
person.sayHi.andCallFake((): boolean => {
22+
return false;
23+
});
24+
// @ts-expect-error
25+
person.sayHi.andCallFake((): number => {
26+
return 100;
27+
});
28+
// @ts-expect-error
29+
person.sayHi.andCallFake((): { prop: boolean } => {
30+
return { prop: true };
31+
});
32+
33+
// Check methods with arguments
34+
person.saySomething.andReturn('');
35+
person.saySomething.withArgs('Testing').and.returnValue('You said: Testing');
36+
person.saySomething.andCallFake((something: string) => {
37+
return `You said: ${something}`;
38+
});
39+
40+
// Check method with different return types
41+
// These shouldn not compile, but will with `@ts-expect-error` applied
42+
// @ts-expect-error
43+
person.saySomething.andReturn(false);
44+
// @ts-expect-error
45+
person.saySomething.andReturn(100);
46+
// @ts-expect-error
47+
person.saySomething.andReturn({ prop: true });
48+
// @ts-expect-error
49+
person.saySomething.andCallFake((str: string): boolean => {
50+
return false;
51+
});
52+
// @ts-expect-error
53+
person.saySomething.andCallFake((str: string): number => {
54+
return 100;
55+
});
56+
// @ts-expect-error
57+
person.saySomething.andCallFake((str: string): { prop: boolean } => {
58+
return { prop: true };
59+
});
60+
// @ts-expect-error
61+
person.saySomething.andCallFake((num: number): string => {
62+
return 'Wrong Argument Type';
63+
});
64+
// @ts-expect-error
65+
person.saySomething.andCallFake((bool: boolean): string => {
66+
return 'Wrong Argument Type';
67+
});
68+
69+
// Check pure void methods
70+
person.voidMethod.andCallFake(() => {});
71+
72+
// Check method with different return types
73+
// These shouldn not compile, but will with `@ts-expect-error` applied
74+
// @ts-expect-error
75+
person.voidMethod.andReturn(false);
76+
// @ts-expect-error
77+
person.voidMethod.andReturn(100);
78+
// @ts-expect-error
79+
person.voidMethod.andReturn({ prop: true });
80+
81+
// These compile because a void method's return type isn't checked by the compiler
82+
person.voidMethod.andCallFake((): boolean => {
83+
return false;
84+
});
85+
person.voidMethod.andCallFake((): number => {
86+
return 100;
87+
});
88+
person.voidMethod.andCallFake((): { prop: boolean } => {
89+
return { prop: true };
90+
});
91+
92+
// Check void methods with arguments
93+
person.voidMethodWithArguments.andCallFake((arg1: string, arg2: number) => {
94+
// Do nothing
95+
});
96+
97+
// Check methods with different argument and return types
98+
// These shouldn not compile, but will with `@ts-expect-error` applied
99+
// @ts-expect-error
100+
person.voidMethod.andReturn(false);
101+
// @ts-expect-error
102+
person.voidMethod.andReturn(100);
103+
// @ts-expect-error
104+
person.voidMethod.andReturn({ prop: true });
105+
// @ts-expect-error
106+
person.voidMethodWithArguments.andCallFake((arg1: string, arg2: string) => {});
107+
// @ts-expect-error
108+
person.voidMethodWithArguments.andCallFake((arg1: number, arg2: string) => {});
109+
110+
// These compile because an empty argument list is valid in Typescript.
111+
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters
112+
person.voidMethodWithArguments.andCallFake(() => {});
10113
});
11114

12115
it('should enable spying on properties', () => {

0 commit comments

Comments
 (0)