Isolate with test doubles
To test a unit without its real dependencies (network, DB, clock, payments) we use test doubles:
- Stub: returns predefined responses. Replaces a dependency so that it
always returns what the test needs (
getUser() => { id: 1 }). - Spy: wraps a real or fake function and records how it was called (how many times, with what arguments). Useful for verifying interactions.
- Mock: a double with programmed expectations: it already knows what calls it should receive and fails if they aren't met. Combines stub (responds) and spy (verifies).
In practice the language is loose: many people call any double a "mock". What matters is the intent: stub = control inputs, spy/mock = verify outputs/interactions.
Dependency injection
To be able to replace a dependency with a double, the function must not create it internally, it must receive it. That is dependency injection:
// Hard to test: the dependency is "welded in".
function register(email) { sendRealEmail(email); }
// Easy to test: we inject the collaborator.
function register(email, sendEmail) { sendEmail(email); }
// In the test we pass a spy and verify it was called.
Test pure functions and endpoints
- Pure functions (same input → same output, no side effects): the easiest. You only assert on the returned value.
- HTTP endpoints: an integration test boots the handler and sends it a request. In Node you use supertest over the Express app:
// Conceptual (supertest doesn't run in this sandbox):
await request(app).get("/users/1").expect(200);
In these exercises we simulate that request/response with crearReq() and
crearRes(), and assert on res.statusCode and res.body.
Assertions (expect)
An assertion declares what must hold; if it doesn't, it throws and the test fails.
The expect(actual).toBe(expected) API is just sugar over an if + throw.
Coverage and its limits
Coverage measures what percentage of lines/branches the suite executed. Useful for finding untested areas, but it doesn't measure quality: you can have 100% coverage and zero useful assertions. High coverage ≠ correct code.
Examples
A hand-made spy
function createSpy() {
const spy = (...args) => { spy.calls.push(args); };
spy.calls = [];
return spy;
}
const send = createSpy();
function register(email, sendEmail) { sendEmail(email); }
register("ana@x.com", send);
console.log(send.calls.length === 1); // true
console.log(send.calls[0][0] === "ana@x.com"); // true