Skip to content

Commit e25bf9d

Browse files
Merge pull request #85 from abdala/promise-shortcut
Add resolves() and rejects() method
2 parents cda5761 + c21c4cb commit e25bf9d

File tree

6 files changed

+82
-10
lines changed

6 files changed

+82
-10
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface Calculator {
1818
add(a: number, b: number): number;
1919
subtract(a: number, b: number): number;
2020
divide(a: number, b: number): number;
21+
async heavyOperation(): Promise<number>;
2122

2223
isEnabled: boolean;
2324
}
@@ -53,6 +54,21 @@ console.log(calculator.add(1, 2)); //prints 9
5354
console.log(calculator.add(1, 2)); //prints undefined
5455
```
5556

57+
## Working with promises
58+
When working with promises you can also use `resolves()` and `rejects()` to return a promise.
59+
60+
```typescript
61+
calculator.heavyOperation(1, 2).resolves(4);
62+
//same as calculator.heavyOperation(1, 2).returns(Promise.resolve(4));
63+
console.log(await calculator.heavyOperation(1, 2)); //prints 4
64+
```
65+
66+
```typescript
67+
calculator.heavyOperation(1, 2).rejects(new Error());
68+
//same as calculator.heavyOperation(1, 2).returns(Promise.reject(new Error()));
69+
console.log(await calculator.heavyOperation(1, 2)); //throws Error
70+
```
71+
5672
## Verifying calls
5773
```typescript
5874
calculator.enabled = true;

spec/index.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,22 @@ test('returning resolved promises works', async t => {
174174
t.is(1338, await substitute.returnPromise());
175175
});
176176

177+
test('resolving other fake works', async t => {
178+
initialize();
179+
180+
const otherSubstitute = Substitute.for<Dummy>();
181+
substitute.returnPromise().resolves(otherSubstitute);
182+
t.is(otherSubstitute, await substitute.returnPromise());
183+
});
184+
185+
test('resolving promises works', async t => {
186+
initialize();
187+
188+
substitute.returnPromise().resolves(1338);
189+
190+
t.is(1338, await substitute.returnPromise());
191+
});
192+
177193
test('class void returns', t => {
178194
initialize();
179195

spec/throws.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Substitute, Arg } from '../src/index'
55
interface Calculator {
66
add(a: number, b: number): number
77
divide(a: number, b: number): number
8+
heavyOperation(): Promise<number>
89
mode: boolean
910
fakeSetting: boolean
1011
}
@@ -16,6 +17,13 @@ test('throws on a method with arguments', t => {
1617
t.throws(() => calculator.divide(1, 0), { instanceOf: Error, message: 'Cannot divide by 0' })
1718
})
1819

20+
test('rejects on a method', async t => {
21+
const calculator = Substitute.for<Calculator>()
22+
calculator.heavyOperation().rejects(new Error('Error'))
23+
24+
await t.throwsAsync(calculator.heavyOperation, { instanceOf: Error, message: 'Error' })
25+
})
26+
1927
test('throws on a property being called', t => {
2028
const calculator = Substitute.for<Calculator>()
2129
calculator.mode.throws(new Error('Property not set'))

src/Transformations.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import { AllArguments } from "./Arguments";
22

33
export type NoArgumentFunctionSubstitute<TReturnType> =
4-
(() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>))
4+
TReturnType extends Promise<infer U> ?
5+
(() => (TReturnType & NoArgumentMockObjectPromise<TReturnType>)) :
6+
(() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>))
57

68
export type FunctionSubstitute<TArguments extends any[], TReturnType> =
7-
((...args: TArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)) &
8-
((allArguments: AllArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>))
9+
TReturnType extends Promise<infer U> ?
10+
((...args: TArguments) => (TReturnType & MockObjectPromise<TArguments, TReturnType>)) &
11+
((allArguments: AllArguments) => (TReturnType & MockObjectPromise<TArguments, TReturnType>)) :
12+
((...args: TArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)) &
13+
((allArguments: AllArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>))
914

1015
export type PropertySubstitute<TReturnType> = (TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>>);
1116

17+
type Unpacked<T> =
18+
T extends Promise<infer U> ? U :
19+
T;
20+
1221
type BaseMockObjectMixin<TReturnType> = {
1322
returns: (...args: TReturnType[]) => void;
1423
throws: (exception: any) => void;
@@ -22,6 +31,16 @@ type MockObjectMixin<TArguments extends any[], TReturnType> = BaseMockObjectMixi
2231
mimicks: (func: (...args: TArguments) => TReturnType) => void;
2332
}
2433

34+
type NoArgumentMockObjectPromise<TReturnType> = NoArgumentMockObjectMixin<TReturnType> & {
35+
resolves: (...args: Unpacked<TReturnType>[]) => void;
36+
rejects: (exception: any) => void;
37+
}
38+
39+
type MockObjectPromise<TArguments extends any[], TReturnType> = MockObjectMixin<TArguments, TReturnType> & {
40+
resolves: (...args: Unpacked<TReturnType>[]) => void;
41+
rejects: (exception: any) => void;
42+
}
43+
2544
export type ObjectSubstitute<T extends Object, K extends Object = T> = ObjectSubstituteTransformation<T> & {
2645
received(amount?: number): TerminatingObject<K>;
2746
didNotReceive(): TerminatingObject<K>;

src/Utilities.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ export enum SubstituteMethods {
1616
didNotReceive = 'didNotReceive',
1717
mimicks = 'mimicks',
1818
throws = 'throws',
19-
returns = 'returns'
19+
returns = 'returns',
20+
resolves = 'resolves',
21+
rejects = 'rejects'
2022
}
2123

2224
export const Nothing = Symbol();

src/states/FunctionState.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,27 @@ export class FunctionState implements ContextState {
130130
}
131131
}
132132

133-
if (property === SubstituteMethods.returns) {
133+
if (property === SubstituteMethods.returns
134+
|| property === SubstituteMethods.resolves
135+
|| property === SubstituteMethods.rejects
136+
) {
134137
return (...returns: any[]) => {
135138
if (!this._lastArgs) {
136139
throw SubstituteException.generic('Eh, there\'s a bug, no args recorded for this return :/')
137140
}
138-
this.returns.push({
139-
returnValues: returns,
140-
returnIndex: 0,
141-
args: this._lastArgs
142-
})
141+
const returnMock: Partial<ReturnMock> = { returnIndex: 0, args: this._lastArgs };
142+
switch (property) {
143+
case SubstituteMethods.returns:
144+
returnMock.returnValues = returns;
145+
break;
146+
case SubstituteMethods.resolves:
147+
returnMock.returnValues = returns.map(value => Promise.resolve(value));
148+
break;
149+
case SubstituteMethods.rejects:
150+
returnMock.returnValues = returns.map(value => Promise.reject(value));
151+
break;
152+
}
153+
this.returns.push(<ReturnMock>returnMock);
143154
this._calls.pop()
144155

145156
if (this.callCount === 0) {

0 commit comments

Comments
 (0)