Skip to content

Instantly share code, notes, and snippets.

@craftybones
Created January 12, 2026 05:28
Show Gist options
  • Select an option

  • Save craftybones/c0efe463827f70730090425eab9a1863 to your computer and use it in GitHub Desktop.

Select an option

Save craftybones/c0efe463827f70730090425eab9a1863 to your computer and use it in GitHub Desktop.

Dependency Injection (DI) with Functions in Deno (JavaScript Only)

We do not inject data. We inject behavior and resources (functions and streams).

✅ Inject what does the work ❌ Do NOT inject the result of the work


❌ Not Dependency Injection: Injecting Values

const approximatelyBad = (x, value) => {
  return x + value - 0.5;
};

This does not improve testability. You still depend on how that value was produced.


✅ Dependency Injection: Injecting Behavior

const approximately = (x, randomFn) => {
  return x + randomFn() - 0.5;
};

Now the function does not care where randomness comes from.

approximately(10, Math.random); // real randomness
approximately(10, () => 0.5); // fake randomness for tests

👉 DI = inject the function, not the number.


DI with Streams in Deno

If your function reads input, the input should be injected, not created inside.


❌ Bad: Creating stdin Inside the Function

export async function takeFromStdin() {
  const input = Deno.stdin.readable;
  // impossible to test without real stdin
}

This forces tests to use real files or real stdin.


✅ Good: Inject the ReadableStream

export async function takeFromStream(inputStream) {
  const reader = inputStream.getReader();
  const chunks = [];

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    chunks.push(value);
  }

  let size = 0;
  for (const c of chunks) size += c.length;

  const merged = new Uint8Array(size);
  let offset = 0;
  for (const c of chunks) {
    merged.set(c, offset);
    offset += c.length;
  }

  return new TextDecoder().decode(merged);
}

Usage from main

const text = await takeFromStream(Deno.stdin.readable);

✅ Injecting a Fake ReadableStream in Tests

No files. No stdin. Just memory.

function stringToStream(text) {
  const encoder = new TextEncoder();
  return new ReadableStream({
    start(controller) {
      controller.enqueue(encoder.encode(text));
      controller.close();
    },
  });
}

Test

Deno.test('reads injected stream', async () => {
  const fakeInput = stringToStream('In a sunny');

  const result = await takeFromStream(fakeInput);

  if (result !== 'In a sunny') {
    throw new Error('Test failed');
  }
});

Output Should Also Be Injected

Same rule for writing.


❌ Bad: Writing Directly to stdout

await Deno.stdout.write(data);

Hard to test.


✅ Good: Inject the WritableStream

export async function writeToStream(outputStream, text) {
  const writer = outputStream.getWriter();
  await writer.write(new TextEncoder().encode(text));
  await writer.close();
}

Usage

await writeToStream(Deno.stdout.writable, 'hello');

✅ Fake WritableStream for Tests

function captureWritable() {
  const chunks = [];

  const stream = new WritableStream({
    write(chunk) {
      chunks.push(chunk);
    },
  });

  return {
    stream,
    getText() {
      let size = 0;
      for (const c of chunks) size += c.length;

      const merged = new Uint8Array(size);
      let offset = 0;
      for (const c of chunks) {
        merged.set(c, offset);
        offset += c.length;
      }

      return new TextDecoder().decode(merged);
    },
  };
}

Test

Deno.test('writes to injected output', async () => {
  const output = captureWritable();

  await writeToStream(output.stream, 'hello');

  if (output.getText() !== 'hello') {
    throw new Error('Test failed');
  }
});

Why File-Based Tests Are Poor DI

Bad test pattern:

const input = await Deno.open('./testInput.txt', { read: true });
const output = await Deno.open('./testOutput.txt', { write: true });

Problems:

  • Uses disk (slow)
  • Needs cleanup
  • Breaks on paths/permissions
  • Tests file system, not your logic

If your function accepts streams, use fake streams, not files.


Rule of Thumb

If your function depends on:

Dependency Inject This
Random function
Time function
Input ReadableStream
Output WritableStream
Network fetch function

👉 Inject how things happen, not what already happened.


One-Sentence Definition

Dependency Injection means your function receives the things that perform work, instead of creating or hard-coding them.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment