Skip to content

Commit 9304492

Browse files
committed
fix: Don't treat any returns as promises
Previously, a `() => any` mocked function would activate the `thenResolve` helpers and require a Promise in `thenReturns`. This is no longer the case, and now `thenReturns` will accept `any`, and the `thenResolve` helper will no longer be available. You can of course still return a promise with `thenReturns(Promise.resolve())`.
1 parent 7150e8f commit 9304492

4 files changed

Lines changed: 63 additions & 90 deletions

File tree

src/return/returns.spec.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('returns', () => {
2323
})
2424
).thenReturn(expectation);
2525

26-
createReturns<number>(
26+
createReturns(
2727
SM.instance(pendingExpectation),
2828
SM.instance(repo)
2929
).thenReturn(23);
@@ -40,10 +40,9 @@ describe('returns', () => {
4040
})
4141
).thenReturn(expectation);
4242

43-
createReturns<number>(
44-
SM.instance(pendingExpectation),
45-
SM.instance(repo)
46-
).thenThrow(error);
43+
createReturns(SM.instance(pendingExpectation), SM.instance(repo)).thenThrow(
44+
error
45+
);
4746
});
4847

4948
it('should set an empty exception', () => {
@@ -55,7 +54,7 @@ describe('returns', () => {
5554
})
5655
).thenReturn(expectation);
5756

58-
createReturns<number>(
57+
createReturns(
5958
SM.instance(pendingExpectation),
6059
SM.instance(repo)
6160
).thenThrow();
@@ -70,10 +69,9 @@ describe('returns', () => {
7069
})
7170
).thenReturn(expectation);
7271

73-
createReturns<number>(
74-
SM.instance(pendingExpectation),
75-
SM.instance(repo)
76-
).thenThrow('foobar');
72+
createReturns(SM.instance(pendingExpectation), SM.instance(repo)).thenThrow(
73+
'foobar'
74+
);
7775
});
7876

7977
it('should set a return promise', async () => {
@@ -87,7 +85,7 @@ describe('returns', () => {
8785
})
8886
).thenReturn(expectation);
8987

90-
createReturns<Promise<number>>(
88+
createReturns(
9189
SM.instance(pendingExpectation),
9290
SM.instance(repo)
9391
).thenReturn(promise);
@@ -102,7 +100,7 @@ describe('returns', () => {
102100
})
103101
).thenReturn(expectation);
104102

105-
createReturns<Promise<number>>(
103+
createReturns(
106104
SM.instance(pendingExpectation),
107105
SM.instance(repo)
108106
).thenResolve(23);
@@ -119,7 +117,7 @@ describe('returns', () => {
119117
})
120118
).thenReturn(expectation);
121119

122-
createReturns<Promise<number>>(
120+
createReturns(
123121
SM.instance(pendingExpectation),
124122
SM.instance(repo)
125123
).thenReject(error);
@@ -134,7 +132,7 @@ describe('returns', () => {
134132
})
135133
).thenReturn(expectation);
136134

137-
createReturns<Promise<number>>(
135+
createReturns(
138136
SM.instance(pendingExpectation),
139137
SM.instance(repo)
140138
).thenReject();
@@ -149,7 +147,7 @@ describe('returns', () => {
149147
})
150148
).thenReturn(expectation);
151149

152-
createReturns<Promise<number>>(
150+
createReturns(
153151
SM.instance(pendingExpectation),
154152
SM.instance(repo)
155153
).thenReject('foobar');

src/return/returns.ts

Lines changed: 40 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { ReturnValue } from '../expectation/repository/return-value';
21
import { ExpectationRepository } from '../expectation/repository/expectation-repository';
2+
import { ReturnValue } from '../expectation/repository/return-value';
33
import { PendingExpectation } from '../when/pending-expectation';
44
import { createInvocationCount, InvocationCount } from './invocation-count';
55

6-
type PromiseStub<R, P> = {
6+
export type PromiseStub<R, P> = {
77
/**
88
* Set the return value for the current call.
99
*
@@ -49,7 +49,7 @@ type PromiseStub<R, P> = {
4949
thenReject(): InvocationCount;
5050
};
5151

52-
type NonPromiseStub<R> = {
52+
export type NonPromiseStub<R> = {
5353
/**
5454
* Set the return value for the current call.
5555
*
@@ -80,11 +80,6 @@ type NonPromiseStub<R> = {
8080
thenThrow(): InvocationCount;
8181
};
8282

83-
// Wrap T in a tuple to prevent distribution in case it's a union.
84-
export type Stub<T> = [T] extends [Promise<infer U>]
85-
? PromiseStub<U, T>
86-
: NonPromiseStub<T>;
87-
8883
const finishPendingExpectation = (
8984
returnValue: ReturnValue,
9085
pendingExpectation: PendingExpectation,
@@ -109,67 +104,42 @@ const getError = (errorOrMessage: Error | string | undefined): Error => {
109104
return new Error();
110105
};
111106

112-
export const createReturns = <R>(
107+
export const createReturns = (
113108
pendingExpectation: PendingExpectation,
114109
repository: ExpectationRepository
115-
): Stub<R> => {
116-
const nonPromiseStub: NonPromiseStub<any> = {
117-
// TODO: merge this with the promise version
118-
thenReturn:
119-
/* istanbul ignore next: because this is overwritten by the promise version */ (
120-
returnValue: any
121-
): InvocationCount =>
122-
finishPendingExpectation(
123-
{ value: returnValue, isError: false, isPromise: false },
124-
pendingExpectation,
125-
repository
126-
),
127-
thenThrow: (errorOrMessage?: Error | string): InvocationCount =>
128-
finishPendingExpectation(
129-
{ value: getError(errorOrMessage), isError: true, isPromise: false },
130-
pendingExpectation,
131-
repository
132-
),
133-
};
134-
135-
const promiseStub: PromiseStub<any, any> = {
136-
thenReturn: (promise: Promise<any>): InvocationCount =>
137-
finishPendingExpectation(
138-
{
139-
value: promise,
140-
isError: false,
141-
// We're setting this to false because we can't distinguish between a
142-
// promise thenReturn and a normal thenReturn.
143-
isPromise: false,
144-
},
145-
pendingExpectation,
146-
repository
147-
),
148-
149-
thenResolve: (promiseValue: any): InvocationCount =>
150-
finishPendingExpectation(
151-
{
152-
value: promiseValue,
153-
isError: false,
154-
isPromise: true,
155-
},
156-
pendingExpectation,
157-
repository
158-
),
159-
160-
thenReject: (errorOrMessage?: Error | string): InvocationCount =>
161-
finishPendingExpectation(
162-
{
163-
value: getError(errorOrMessage),
164-
isError: true,
165-
isPromise: true,
166-
},
167-
pendingExpectation,
168-
repository
169-
),
170-
};
171-
172-
// @ts-expect-error because the return type is a conditional, and we're merging
173-
// both branches here
174-
return { ...nonPromiseStub, ...promiseStub };
175-
};
110+
) => ({
111+
thenReturn: (returnValue: any): InvocationCount =>
112+
finishPendingExpectation(
113+
// This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)).
114+
{ value: returnValue, isError: false, isPromise: false },
115+
pendingExpectation,
116+
repository
117+
),
118+
thenThrow: (errorOrMessage?: Error | string): InvocationCount =>
119+
finishPendingExpectation(
120+
{ value: getError(errorOrMessage), isError: true, isPromise: false },
121+
pendingExpectation,
122+
repository
123+
),
124+
thenResolve: (promiseValue: any): InvocationCount =>
125+
finishPendingExpectation(
126+
{
127+
value: promiseValue,
128+
isError: false,
129+
isPromise: true,
130+
},
131+
pendingExpectation,
132+
repository
133+
),
134+
135+
thenReject: (errorOrMessage?: Error | string): InvocationCount =>
136+
finishPendingExpectation(
137+
{
138+
value: getError(errorOrMessage),
139+
isError: true,
140+
isPromise: true,
141+
},
142+
pendingExpectation,
143+
repository
144+
),
145+
});

src/when/when.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { getActiveMock, getMockState } from '../mock/map';
22
import { Mode, setMode } from '../mock/mock';
3-
import { createReturns, Stub } from '../return/returns';
3+
import { createReturns, NonPromiseStub, PromiseStub } from '../return/returns';
44

5+
interface When {
6+
<R>(expectation: () => Promise<R>): PromiseStub<R, Promise<R>>;
7+
<R>(expectation: () => R): NonPromiseStub<R>;
8+
}
59
/**
610
* Set an expectation on a mock.
711
*
@@ -27,13 +31,12 @@ import { createReturns, Stub } from '../return/returns';
2731
* const fn = mock<(x: number) => Promise<number>();
2832
* when(() => fn(23)).thenResolve(42);
2933
*/
30-
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
31-
export const when = <R>(expectation: () => R): Stub<R> => {
34+
export const when: When = <R>(expectation: () => R) => {
3235
setMode(Mode.EXPECT);
3336
expectation();
3437
setMode(Mode.CALL);
3538

3639
const { pendingExpectation, repository } = getMockState(getActiveMock());
3740

38-
return createReturns<R>(pendingExpectation, repository);
41+
return createReturns(pendingExpectation, repository);
3942
};

tests/types.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ it('type safety', () => {
4242
// @ts-expect-error promises only reject
4343
when(() => fnp()).thenThrow;
4444

45-
// any will be treated like a promise but should allow any return value.
45+
// any should not enable the promise helpers
4646
const fnany = mock<() => any>();
4747
when(() => fnany()).thenReturn(23);
48+
// @ts-expect-error
49+
when(() => fnany()).thenResolve(23);
4850
}
4951

5052
function matcherSafety() {

0 commit comments

Comments
 (0)