JavaScript test spies, stubs and mocks for framework-agnostic unit testing.
—
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.
Create Promise instances that can be manually resolved or rejected at any time during testing.
/**
* Creates a controllable Promise for testing
* @param executor - Optional executor function (same as standard Promise)
* @returns Controllable promise with additional methods and properties
*/
function promise<T = any>(executor?: PromiseExecutor<T>): SinonPromise<T>;
interface SinonPromise<T> extends Promise<T> {
/** Current status of the promise */
status: "pending" | "resolved" | "rejected";
/** Value the promise resolved with (if resolved) */
resolvedValue?: T;
/** Reason the promise rejected with (if rejected) */
rejectedValue?: any;
/** Manually resolve the promise with a value */
resolve(value?: T): SinonPromise<T>;
/** Manually reject the promise with a reason */
reject(reason?: any): Promise<void>;
}Usage Examples:
import { promise } from "sinon";
// Create a controllable promise
const controllablePromise = promise();
// Check initial status
console.log(controllablePromise.status); // "pending"
// Set up promise handlers
controllablePromise.then(value => {
console.log("Resolved with:", value);
}).catch(error => {
console.log("Rejected with:", error);
});
// Manually resolve
controllablePromise.resolve("test data");
console.log(controllablePromise.status); // "resolved"
console.log(controllablePromise.resolvedValue); // "test data"Create controllable promises with initial executor logic.
type PromiseExecutor<T> = (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void;Usage Examples:
import { promise } from "sinon";
// Promise with executor that doesn't resolve immediately
const delayedPromise = promise((resolve, reject) => {
// Executor runs but doesn't resolve yet
console.log("Promise created but not resolved");
});
// Later in test, manually control resolution
delayedPromise.resolve("manual resolution");Use controllable promises to test functions that work with async operations.
Usage Examples:
import sinon from "sinon";
// Function under test
async function processData(dataFetcher) {
try {
const data = await dataFetcher();
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
// Test with controllable promise
describe("processData", () => {
it("should handle successful data fetch", async () => {
const mockFetcher = sinon.stub();
const controllablePromise = sinon.promise();
mockFetcher.returns(controllablePromise);
// Start the async operation
const resultPromise = processData(mockFetcher);
// Verify promise is still pending
expect(controllablePromise.status).to.equal("pending");
// Manually resolve with test data
controllablePromise.resolve({ id: 1, name: "Test" });
// Await the result
const result = await resultPromise;
expect(result.success).to.be.true;
expect(result.data).to.deep.equal({ id: 1, name: "Test" });
expect(controllablePromise.status).to.equal("resolved");
});
it("should handle failed data fetch", async () => {
const mockFetcher = sinon.stub();
const controllablePromise = sinon.promise();
mockFetcher.returns(controllablePromise);
const resultPromise = processData(mockFetcher);
// Manually reject with test error
controllablePromise.reject(new Error("Network error"));
const result = await resultPromise;
expect(result.success).to.be.false;
expect(result.error).to.equal("Network error");
expect(controllablePromise.status).to.equal("rejected");
});
});Monitor promise state changes during testing for precise async behavior verification.
Usage Examples:
import { promise } from "sinon";
const testPromise = promise();
// Initial state
assert.equal(testPromise.status, "pending");
assert.isUndefined(testPromise.resolvedValue);
assert.isUndefined(testPromise.rejectedValue);
// After resolution
testPromise.resolve("success");
assert.equal(testPromise.status, "resolved");
assert.equal(testPromise.resolvedValue, "success");
assert.isUndefined(testPromise.rejectedValue);
// Testing rejection
const rejectPromise = promise();
rejectPromise.reject("failure");
assert.equal(rejectPromise.status, "rejected");
assert.equal(rejectPromise.rejectedValue, "failure");
assert.isUndefined(rejectPromise.resolvedValue);Combine controllable promises with stubs for comprehensive async testing.
Usage Examples:
import sinon from "sinon";
class ApiClient {
async fetchUser(id) {
// Implementation that makes HTTP request
return fetch(`/api/users/${id}`).then(r => r.json());
}
}
describe("ApiClient", () => {
let client, fetchStub, controllablePromise;
beforeEach(() => {
client = new ApiClient();
controllablePromise = sinon.promise();
// Stub fetch to return controllable promise
fetchStub = sinon.stub(global, "fetch");
fetchStub.returns(controllablePromise);
});
afterEach(() => {
sinon.restore();
});
it("should fetch user data", async () => {
const userPromise = client.fetchUser(123);
// Verify fetch was called correctly
sinon.assert.calledOnce(fetchStub);
sinon.assert.calledWith(fetchStub, "/api/users/123");
// Simulate successful HTTP response
const mockResponse = {
json: sinon.stub().returns(Promise.resolve({ id: 123, name: "John" }))
};
controllablePromise.resolve(mockResponse);
const user = await userPromise;
expect(user).to.deep.equal({ id: 123, name: "John" });
});
});type PromiseExecutor<T> = (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void;
interface SinonPromise<T> extends Promise<T> {
status: "pending" | "resolved" | "rejected";
resolvedValue?: T;
rejectedValue?: any;
resolve(value?: T): SinonPromise<T>;
reject(reason?: any): Promise<void>;
}Install with Tessl CLI
npx tessl i tessl/npm-sinon