Dependency Injection

The Course
•
5 min read

Replace hidden globals with explicit operations

A dependency is something a function relies on: a request operation, clock, storage object, or renderer. Passing it as an argument is dependency injection. You already passed callbacks; now use that technique to test behavior under chosen conditions.

An adapter can accept request and basePath rather than importing a global fetch or hard-coding a production server. A fake records the URL it received and returns a controlled response. That lets a test check not only output but the request contract.

URLSearchParams builds an encoded query string from fields. It handles spaces and separators in a search term, avoiding a hand-built URL where an ampersand becomes another parameter. An HTTP URL is still not a place for private credentials. In this course the value is a non-sensitive catalogue search.

Testing a fake verifies how your code uses that interface. It does not prove an actual remote server implements the interface. A real integration check belongs at the boundary of a service you control, with known responses and CORS configuration. Keep network-dependent checks separate from the deterministic lesson tests.

Predict and trace

Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.

Predict, then run JavaScript
async function main() {
  async function search(request, query) {
    const params = new URLSearchParams({ q: query });
    const response = await request(`/books?${params}`);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
  }

  const calls = [];
  const request = async url => {
    calls.push(url);
    return { ok: true, json: async () => [] };
  };

  await search(request, "web & js");
  console.log(calls[0]);
}

main();
Check your prediction
/books?q=web+%26+js

The ampersand is encoded inside the q value. It does not introduce a second query parameter.

The real-service extension

The Day 7 download includes http-service.js, an optional real HTTP adapter. It accepts request and an absolute endpoint, constructs a URL, sets its query with url.searchParams.set("q", query), and passes the signal through to request. A URL object separates the address from its query fields; it avoids manually joining punctuation.

Passing the browser’s fetch supplies the real operation. The endpoint must belong to a service whose contract and CORS policy you understand; changing a string does not make an arbitrary API compatible. The demo stays local, and the adapter’s tests use doubles to check URL encoding, status, body failure, and cancellation forwarding without any live requests.

Use a dependency to observe the boundary

An injected function gives tests a place to observe calls without making a real request. A fake can record arguments and return a deterministic response. This lets you test your adapter’s behavior separately from network availability.

Keep fake behavior within the real dependency contract: an async request returns a promise, and a JSON method can fail independently. A fake that returns a finished array directly can make a broken adapter appear correct because it skips the response boundary.

Use a dependency to observe the boundary: trace JavaScript
async function main() {
  const calls = [];
  const request = async url => {
    calls.push(url);
    return { ok: true };
  };

  await request("/books?q=loops");
  console.log(calls.length);
  console.log(calls[0]);
}

main();
Check your prediction
1
/books?q=loops

The fake preserves the async call shape and reveals the exact argument. A test can now detect duplicate requests or an incorrectly formed query without a live server.

Use a dependency to observe the boundary: contrast JavaScript
async function main() {
  const request = async () => ({
    ok: true,
    json: async () => {
      throw new Error("Bad JSON");
    }
  });
  const response = await request();

  try {
    await response.json();
  } catch (error) {
    console.log(error.message);
  }
}

main();
Check your prediction
Bad JSON

Successful transport and successful parsing are separate operations. Make the fake capable of distinguishing them before claiming your tests cover response failures.

Try the rule on new inputs

Record two calls with distinct queries and assert both order and arguments. Then supply a rejecting request fake and a rejecting json fake. Identify which layer is responsible for explaining each failure.

Build it yourself

Exercise

Dependency Injection: practise

Write createLoader(request), returning an async function that requests /books and returns parsed JSON after checking ok. Use a double that counts calls. Print the returned length and call count.

Your working space JavaScript
// The returned function closes over request.
Hint 1: the idea

Combine the closure factory from Day 2 with the adapter from today.

Hint 2: a first step

Increment the counter inside the double rather than inside the loader.

Show one solution
async function main() {
  function createLoader(request) {
    return async function load() {
      const response = await request("/books");

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return await response.json();
    };
  }

  let calls = 0;
  const load = createLoader(async () => {
    calls += 1;
    return { ok: true, json: async () => ["Notes"] };
  });

  console.log((await load()).length);
  console.log(calls);
}

main();
1
1

Call the loader twice and verify the fake sees two calls. Then reject the second call and confirm the first result is not retroactively changed.

Test a changed requirement

Call the loader twice and verify the fake sees two calls. Then reject the second call and confirm the first result is not retroactively changed.

Explain before moving on

Which behavior belongs in the adapter and which in its dependency? Why does a fake not replace a server integration test?

Was this article helpful?

Your response is saved on this device.