0
# Promise Utilities
1
2
Sinon's promise utilities provide controllable Promise implementations for testing asynchronous code without relying on timing or external Promise resolution. These utilities allow you to manually control when Promises resolve or reject, making async tests deterministic and fast.
3
4
## Capabilities
5
6
### Creating Controllable Promises
7
8
Create Promise instances that can be manually resolved or rejected at any time during testing.
9
10
```javascript { .api }
11
/**
12
* Creates a controllable Promise for testing
13
* @param executor - Optional executor function (same as standard Promise)
14
* @returns Controllable promise with additional methods and properties
15
*/
16
function promise<T = any>(executor?: PromiseExecutor<T>): SinonPromise<T>;
17
18
interface SinonPromise<T> extends Promise<T> {
19
/** Current status of the promise */
20
status: "pending" | "resolved" | "rejected";
21
22
/** Value the promise resolved with (if resolved) */
23
resolvedValue?: T;
24
25
/** Reason the promise rejected with (if rejected) */
26
rejectedValue?: any;
27
28
/** Manually resolve the promise with a value */
29
resolve(value?: T): SinonPromise<T>;
30
31
/** Manually reject the promise with a reason */
32
reject(reason?: any): Promise<void>;
33
}
34
```
35
36
**Usage Examples:**
37
38
```javascript
39
import { promise } from "sinon";
40
41
// Create a controllable promise
42
const controllablePromise = promise();
43
44
// Check initial status
45
console.log(controllablePromise.status); // "pending"
46
47
// Set up promise handlers
48
controllablePromise.then(value => {
49
console.log("Resolved with:", value);
50
}).catch(error => {
51
console.log("Rejected with:", error);
52
});
53
54
// Manually resolve
55
controllablePromise.resolve("test data");
56
console.log(controllablePromise.status); // "resolved"
57
console.log(controllablePromise.resolvedValue); // "test data"
58
```
59
60
### Promise with Executor
61
62
Create controllable promises with initial executor logic.
63
64
```javascript { .api }
65
type PromiseExecutor<T> = (
66
resolve: (value: T | PromiseLike<T>) => void,
67
reject: (reason?: any) => void
68
) => void;
69
```
70
71
**Usage Examples:**
72
73
```javascript
74
import { promise } from "sinon";
75
76
// Promise with executor that doesn't resolve immediately
77
const delayedPromise = promise((resolve, reject) => {
78
// Executor runs but doesn't resolve yet
79
console.log("Promise created but not resolved");
80
});
81
82
// Later in test, manually control resolution
83
delayedPromise.resolve("manual resolution");
84
```
85
86
### Testing Async Functions
87
88
Use controllable promises to test functions that work with async operations.
89
90
**Usage Examples:**
91
92
```javascript
93
import sinon from "sinon";
94
95
// Function under test
96
async function processData(dataFetcher) {
97
try {
98
const data = await dataFetcher();
99
return { success: true, data };
100
} catch (error) {
101
return { success: false, error: error.message };
102
}
103
}
104
105
// Test with controllable promise
106
describe("processData", () => {
107
it("should handle successful data fetch", async () => {
108
const mockFetcher = sinon.stub();
109
const controllablePromise = sinon.promise();
110
111
mockFetcher.returns(controllablePromise);
112
113
// Start the async operation
114
const resultPromise = processData(mockFetcher);
115
116
// Verify promise is still pending
117
expect(controllablePromise.status).to.equal("pending");
118
119
// Manually resolve with test data
120
controllablePromise.resolve({ id: 1, name: "Test" });
121
122
// Await the result
123
const result = await resultPromise;
124
125
expect(result.success).to.be.true;
126
expect(result.data).to.deep.equal({ id: 1, name: "Test" });
127
expect(controllablePromise.status).to.equal("resolved");
128
});
129
130
it("should handle failed data fetch", async () => {
131
const mockFetcher = sinon.stub();
132
const controllablePromise = sinon.promise();
133
134
mockFetcher.returns(controllablePromise);
135
136
const resultPromise = processData(mockFetcher);
137
138
// Manually reject with test error
139
controllablePromise.reject(new Error("Network error"));
140
141
const result = await resultPromise;
142
143
expect(result.success).to.be.false;
144
expect(result.error).to.equal("Network error");
145
expect(controllablePromise.status).to.equal("rejected");
146
});
147
});
148
```
149
150
### Promise Status Inspection
151
152
Monitor promise state changes during testing for precise async behavior verification.
153
154
**Usage Examples:**
155
156
```javascript
157
import { promise } from "sinon";
158
159
const testPromise = promise();
160
161
// Initial state
162
assert.equal(testPromise.status, "pending");
163
assert.isUndefined(testPromise.resolvedValue);
164
assert.isUndefined(testPromise.rejectedValue);
165
166
// After resolution
167
testPromise.resolve("success");
168
assert.equal(testPromise.status, "resolved");
169
assert.equal(testPromise.resolvedValue, "success");
170
assert.isUndefined(testPromise.rejectedValue);
171
172
// Testing rejection
173
const rejectPromise = promise();
174
rejectPromise.reject("failure");
175
assert.equal(rejectPromise.status, "rejected");
176
assert.equal(rejectPromise.rejectedValue, "failure");
177
assert.isUndefined(rejectPromise.resolvedValue);
178
```
179
180
### Integration with Stubs
181
182
Combine controllable promises with stubs for comprehensive async testing.
183
184
**Usage Examples:**
185
186
```javascript
187
import sinon from "sinon";
188
189
class ApiClient {
190
async fetchUser(id) {
191
// Implementation that makes HTTP request
192
return fetch(`/api/users/${id}`).then(r => r.json());
193
}
194
}
195
196
describe("ApiClient", () => {
197
let client, fetchStub, controllablePromise;
198
199
beforeEach(() => {
200
client = new ApiClient();
201
controllablePromise = sinon.promise();
202
203
// Stub fetch to return controllable promise
204
fetchStub = sinon.stub(global, "fetch");
205
fetchStub.returns(controllablePromise);
206
});
207
208
afterEach(() => {
209
sinon.restore();
210
});
211
212
it("should fetch user data", async () => {
213
const userPromise = client.fetchUser(123);
214
215
// Verify fetch was called correctly
216
sinon.assert.calledOnce(fetchStub);
217
sinon.assert.calledWith(fetchStub, "/api/users/123");
218
219
// Simulate successful HTTP response
220
const mockResponse = {
221
json: sinon.stub().returns(Promise.resolve({ id: 123, name: "John" }))
222
};
223
224
controllablePromise.resolve(mockResponse);
225
226
const user = await userPromise;
227
expect(user).to.deep.equal({ id: 123, name: "John" });
228
});
229
});
230
```
231
232
## Types
233
234
```javascript { .api }
235
type PromiseExecutor<T> = (
236
resolve: (value: T | PromiseLike<T>) => void,
237
reject: (reason?: any) => void
238
) => void;
239
240
interface SinonPromise<T> extends Promise<T> {
241
status: "pending" | "resolved" | "rejected";
242
resolvedValue?: T;
243
rejectedValue?: any;
244
resolve(value?: T): SinonPromise<T>;
245
reject(reason?: any): Promise<void>;
246
}
247
```