4

I recently started a node project and as a Test-Driven Developer, I quickly ran into a dependency injection problem with my brand new module. Here's how I figured out I should do dependency injection. It's important to note I'm using vows as BDD framework and extend it with Sinon.

My module:

exports.myMethod = function () {
  var crypto = exports.cryptoLib || require('ezcrypto').Crypto;
  crypto.HMAC(
    crypto.SHA256,
    'I want to encrypt this',
    'with this very tasty salt'
  );
};

My test:

var vows = require('vows'),
  sinon = require('sinon');

vows.describe('myObject').addBatch({
  'myMethod':{
    'topic':true,
    'calls ezcrypto.HMAC':function () {
      var myObject = require('../playground.js');
      var mock = sinon.mock(require('ezcrypto').Crypto);

      myObject.cryptoLib = mock;
      myObject.cryptoLib.HMAC = mock.expects("HMAC").once().withExactArgs(
        require('ezcrypto').Crypto.SHA256,
        'I want to encrypt this',
        'with this very tasty salt'
      );
      myObject.cryptoLib.SHA256 = require('ezcrypto').Crypto.SHA256;
      myObject.cryptoLib = mock;
      myObject.myMethod();
      mock.verify();
    }
  }
}).export(module);

Do you think this the correct way to go? I like this solution because it doesn't require more when you use the module (like adding "()" after the require statement).

1 Answers1

3

It's not good way to trash your code with test stuff. Line 2 of your code

var crypto = exports.cryptoLib || require('ezcrypto').Crypto;

Looks like unnecesary interface. I suggest you to replace it with

var crypto = require('ezcrypto').Crypto;

Much cleaner. And in test just mock Crypto method of 'ezcrypto' module. Just do not forget to revert it back after using.

Anatoliy
  • 29,485
  • 5
  • 46
  • 45
  • Can you show me I should mock Crypto and inject the mock in my module during the test please? – Simon Jodet Mar 21 '12 at 06:10
  • 1
    It's dead-simple: `var oldCrypto = require('ezcrypto').Crypto; require('ezcrypto').Crypto = MyCrypto; // mock // do tests` – Anatoliy Mar 21 '12 at 15:23
  • 1
    `require()` guarantees that it returned a memorized result, which is what enables this – Nevir Mar 21 '12 at 21:20
  • I still have mixed feelings about this. Not that it wouldn't work but using the global domain to inject dependencies (not only for test BTW) makes me uncomfortable. Especially knowing that test suites run tests asynchronously. I wonder if there is not a chance other tests would get screwed by the globally overridden object. – Simon Jodet Mar 22 '12 at 05:59
  • I think running tests in parallel is not good idea. But when running sequentially there's no problem to override whole module. It even better than trash your code with test-only interfaces. – Anatoliy Mar 22 '12 at 09:12
  • TDD is all about test independence. Please read Kent Beck TDD by example. It's very important to have independent tests. That's why test frameworks don't bother running tests sequentially. I'm not even sure you can configure Vows or Jasmine to run tests sequentially. – Simon Jodet Apr 02 '12 at 08:08