๐Ÿ“ฃ Why Promises Should Be Mockable

๐Ÿ“ฃ Why Promises Should Be Mockable

“Asynchronous code is easy to write — but hard to test.”

Promises revolutionized async programming by replacing callback hell with chainable logic. But when it comes to testing, native Promises behave like black boxes: once a Promise starts, it runs independently — out of your control.

If your app depends on multiple async sources — APIs, WebSockets, or events — you likely faced this problem: you can’t pause, manipulate, or mock Promises mid-flow. That’s exactly what MockChain solves.


๐Ÿ’ก The Idea

MockChain is a drop-in replacement for Promise, designed for one purpose:

To make asynchronous operations controllable, testable, and deterministic.

Instead of waiting for a real async event, you can inject outcomes in your tests — as if the async process had completed.

import { MockChain } from '@fizzwiz/mockchain';

// In your code, implement an async flow by a MockChain
const chain = new MockChain()
  .then(data => console.log(data));

// In your tests, simulate an async result by mocking the root of the chain
chain.root.mock({ ok: true });
// → { ok: true }

It looks like a normal Promise. It acts like a normal Promise. But unlike a normal Promise — you control when it resolves.


๐Ÿง  Why This Matters

Asynchronous operations are everywhere:

  • Fetching data from APIs
  • Waiting for WebSocket messages
  • Reacting to events in distributed systems

Testing them is hard because:

  • They depend on time and external state
  • They may involve race conditions
  • They can trigger side effects unpredictably

With MockChain, you can simulate these conditions without real latency or external dependencies.


⚙️ A Practical Example

import { MockChain } from '@fizzwiz/mockchain';

// Your async-like function
function loadUserProfile(id, autoStart = true) {
  return MockChain.fromResponse(`https://api/user/${id}`, {}, autoStart)
    .then(res => res.json());
}

// Your test
const chain = loadUserProfile(undefined, false);
chain.root.mock(new Response(JSON.stringify({ name: "Ada Lovelace" })));
const user = await chain;
assert.deepEqual(user, { name: "Ada Lovelace" });

You’ve just tested a method that depends on fetch  without touching the network.


๐Ÿ› ️ Mock Everything

You can mock any async source:

Source TypeFactory Method.mock() Expects
Fetch requestfromResponse(url, opts, autoStart)a Response object
WebSocket messagefromMessage(ws, isMessage, attach, autoStart)a MessageEvent or { data: ... }
Event emitterfromEvent(emitter, eventName, isEvent, attach, autoStart)the emitted event args

Each method name suggests what value .mock() should take — letting you simulate runtime behavior precisely.


⚡ Control Time and Errors

const delayed = new MockChain();
delayed.mockAfter(1000, "Done");           // resolve after 1s
delayed.failAfter(2000, new Error("Timeout")); // reject after 2s

These features make MockChain ideal for CI, unit testing, and fault injection — where timing and state consistency matter.


๐Ÿ”ฎ The Vision

MockChain was created to bring deterministic testing to inherently nondeterministic systems. By allowing developers to control async behavior directly, it bridges the gap between real-world concurrency and controlled testing environments — especially in distributed architectures.

Ultimately, this vision points beyond MockChain itself:

JavaScript could evolve to make built-in Promises inherently mockable — enabling developers to pause, inject, and control asynchronous flows without wrapping or replacing native behavior.

Such a change would bring first-class testability to async logic, making “mockable Promises” as natural as “awaitable Promises.”


Comments

Popular posts from this blog

๐Ÿ”‘ Why MockChain Is a Chain

๐Ÿ The autostart Flag

⚠️ Async Functions Are Not Mockable