Skip to content

Instantly share code, notes, and snippets.

@jeremyckahn
Created May 2, 2018 01:44
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyckahn/c8faeb9131670eca5496ee57f114b89d to your computer and use it in GitHub Desktop.
Save jeremyckahn/c8faeb9131670eca5496ee57f114b89d to your computer and use it in GitHub Desktop.
Language-level dependency injection with ES6

Language-level dependency injection with ES6

What follows is a pretty shameless abuse of one of my favorite ES6 features, default parameters. They're a simple idea that other languages have enjoyed for decades, and I am all too happy to see JavaScript join the party.

const logValue = (object = {}) => {
  console.log(object);
};

logValue();  // logs: {}
logValue(5); // logs: 5

Beautiful! Reading deeper into the docs reveals that we can set more than static data as a default parameter. We can set entire expressions:

const logValue = (value = 1 + 1) => {
  console.log(value);
};

logValue();  // logs: 2
logValue(5); // logs: 5

Neat! Your logic can be pretty much anything; the function above is effectively interpreted as:

const logValue = value => {
  value = value === undefined ? 1 + 1 : value;
  console.log(value);
};

...But let's stick with default parameters; they're a little cleaner.

Abusing the language for fun and profit

So now that we have this handy tool at our disposal, what sort of mad science could we get up to with it? Why, dependency injection, of course! DI is super handy for testing code, and there is no shortage of contortions and libraries to achieve in JavaScript what other languages have as a first-class feature.

Let's say that we need to test a function that depends on some window variables to work. It's not ideal, but we've all been there:

const leave = destination => {
  window.location = destination;
};

If a unit test calls this in a browser environment, the browser will navigate to destination and the tests will never complete. Bummer! But we can easily inject a stub window object that won't touch the global state:

const leave = (destination, _window = window) => {
  _window.location = destination;
};

So now leave will only use window unless an alternative value is not provided. We can write our test like:

describe('leave', () => {
  it('goes away', () => {
    const win = {};
    leave('http://jort.technology/', win);

    assert.equal(win.location, 'http://jort.technology/');
  });
});

Success; the test validates that the function works and window remains unchanged!

Because a default parameter can be anything, this is a simple way to stub modules or libraries that a function might depend on without needing to add complex infrastructure to your project.

This isn't a fit for every project, but it is a simple way to get dependency injection with minimal infrastructure overhead!

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