9

I'm learning about dependency injection and while I can see the appeal of it when writing functional libraries, I fail to see how it solves anything when you'll also be the one using the libraries.

It makes testing the library much simpler, because there isn't much to test.

But you will eventually have to test your injected function when you use the library and have to deal with mocking and stubbing functions from the standard library.

This is a concrete case I'm dealing with in Node.js:

function compile(options) {
  var files = options.files;
  var texCompiler = options.texCompiler;
  var pdfMerger = options.pdfMerger;

  return Promise.all(files.map(texCompiler(files)))
    .then(pdfMerger);
}

That is trivial to test: injecting mock objects or spies as the texCompiler and pdfMerger is a piece of cake because the function really doesn't do much at all. All I can test is that both functions are called in the right sequence.

It doesn't save me from testing my texCompiler and pdfMerger functions eventually though. They look something like that:

var tex2Pdf = Promise.method(function tex2Pdf(tex_doc) {
    var latex_command = 'pdflatex';
    var pdf_output_filename = path.parse(tex_doc).name + '.pdf';
    var cmd = latex_command + ' ' + tex_doc;
    var options = {
      cwd: path.resolve(tex_doc, '..') // pdflatex will only look for custom
      // cls files in the cwd and includes relative to the cwd
    };
    child_process.spawn(cmd, options)
      .on('end', function() {
        console.log('tex2Pdf finish');
        debugger;
        return path.resolve(tex_doc, '..', pdf_output_filename);
      })
      .on('error', function(e) {
        throw e;
      });
});


var mergeTwoPdf = Promise.method(function mergeTwoPdf(pdf_files) {
  var output_file = randomId() + '.pdf';
  var cmd = 'gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=' + output_file + ' ' + pdf_files[0] + ' ' + pdf_file[1];
  child_process.spawn(cmd)
    .on('finish', function() {
      return output_file;
    })
  .on('error', function(e) {
    throw (e);
  });
});

This is the real thing, and it is a bigger pain to test. I have to mock child_process.spawn with a spy to make sure it gets called with the right arguments, but it doesn't actually do anything, because I don't want to actually merge any PDF file when running the tests and my mocks have to emit the right events so the function isn't stuck.

These are problems I would have had if I didn't inject the dependency in my first snippet and used these functions instead. And it really feels like I'm pushing the problem further down without solving it.

Am I misunderstanding dependency injection? Am I doing it wrong?

8

You're missing the point of testing it seems.

That is trivial to test: injecting mocks or spies as the texCompiler and pdfMerger is a piece of cake because the function really doesn't do much at all. All I can test is that both functions are called in the right sequence.

Awesome! If the function is doing trivial work, then the test should be trivial. Likewise, if all that function is actually doing is calling A then B, you're not validating much of your code.

The entire point of good tests are to isolate what you're testing, so when the test fails, you can better know what went wrong. Dependency injection helps that.

And it really feels like I'm pushing the problem further down without solving it.

Sure, in this case you're not "solving it" because your code has nasty dependencies that are hard to isolate. You need to work with the filesystem. You need to call these external processes. Sometimes that is the nature of the beast.

Dependency Injection doesn't remove dependencies, it just flips them around so that you can choose what they are. That helps isolate code for testing. It helps the code be more flexible when those dependencies inevitably change. But it does not kill them - your code still needs to be able to do all the same things somewhere.

  • That makes sense. Is there anything else I could do to make it easier to test those nasty dependencies? – springloaded Oct 13 '15 at 18:09
  • 2
    @springloaded - just pragmatism. Isolating dependencies is good, but in an app like this, it doesn't make much sense. Integration tests are worse than unit test (take longer, are more fragile, are harder to tie to a cause) but if unit tests aren't going to give you much, they might not be worth the time. – Telastyn Oct 13 '15 at 18:16
2

I think it's the testing you are misunderstanding if anything.

I would test your PDF file creation by creating a PDF file and comparing it with a known good one.

I would test your PDF file merge by merging two PDF files and comparing the output with, again, a known good file.

  • 2
    So you're saying I should do integration tests on this code but no unit test? In that context, does dependency injection even make sense? – springloaded Oct 13 '15 at 17:20
  • 3
    @springloaded: You make a valid point. I would only say that, just because DI doesn't make sense in your particular scenario, doesn't necessarily mean that it fails in all scenarios. Dealing with external libraries is primarily an integration story anyway; the point of DI is to make that integration wireable rather than baking it into your code directly. – Robert Harvey Oct 13 '15 at 17:26
  • @RobertHarvey Sure, I see how DI can be useful outside of my use case – springloaded Oct 13 '15 at 17:46
  • hmm I'd say ts not an integration test at that level. If you like you could output the raw data and inject the write to disc bit if you want it more 'unity'. – Ewan Oct 13 '15 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.