Mike Rifgin
Mike Rifgin

Reputation: 10761

Mixing object construction with application logic

I've started refactoring my code to make it testable. One area I've found a problem is where I've mixed object construction with application logic. If I have a constructor called SomeClass which performs application logic but also instantiates another class I run in to problems when I try to test:

function SomeClass() {

    //does lots of application type work
    //but then also instantiates a different object
    new AnotherClass();
}

Testing becomes difficult because now I need to find a way to create AnotherClass in the test environment.

I've dealt with this problem using dependency injection. So SomeClass takes an instance of AnotherClass as a parameter:

function SomeClass(anotherObj) {

}

Problem with this is as far as I can see is all this does is defer the problem to somewhere else in my application. I still have to create the anotherObj from AnotherClass somewhere else in my code.

This google testing article http://googletesting.blogspot.co.uk/2008/08/by-miko-hevery-so-you-decided-to.html suggests:

In order to have a testable code-base your application should have two kinds of classes. The factories, these are full of the "new" operators and are responsible for building the object graph of your application, but don't do anything.And the application logic classes which are devoid of the "new" operator and are responsible for doing work.

This sounds exactly like my problem and the factory type pattern is what I need. So I tried something like:

function anotherClassFactory() {
    return new AnotherClass();
}

function SomeClass() {
    anotherClassFactory();
}

But then SomeClass still has a dependency on the factory. How do I get around this correctly?

Upvotes: 2

Views: 137

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075169

(I'm making this a community wiki answer because I frankly think it only answers part of the question, while leaving too much unsaid. Hopefully others with more knowledge can improve it.)

But then SomeClass still has a dependency on the factory. How do I get around this correctly?

According to this article linked by the one you linked, you do it like this:

anotherclass.js:

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };

someclass.js:

function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}

someclass-test.js:

function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}

app.js:

function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}

So the real app is built using buildApp, which gives SomeClass its AnotherClass instance. Your tests for SomeClass would use someClass_testSomething and such, which uses the real SomeClass but a mocked-up instance rather than a real AnotherClass containing just enough of it for the purposes of the test.

My dependency-injection-fu is weak, though, and I frankly don't see how buildApp scales to the real world, nor do I see what you're supposed to do if a method has to create an object to do its job, e.g.:

SomeClass.prototype.doSomething = function() {
    // Here, I need an instance of AnotherClass; maybe I even need to
    // create a variable number of them, depending on logic internal
    // to the method.
};

You're not going to pass everything the method needs as arguments, it would be a spaghetti nightmare. This is probably why for more static languages, there are usually tools rather than just coding patterns involved.


In JavaScript, of course, we have another option: Just go ahead and use new AnotherClass in the code:

anotherclass.js:

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };

someclass.js:

function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}

someclass-test.js:

var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}

You use anotherclass.js and someclass.js in your real app, and you use someclass.js and someclass-test.js when testing SomeClass.

That's a rough sketch, of course; I'm assuming your real-world app probably doesn't have globals (SomeClass, AnotherClass) all over the place, but however it is you're containing SomeClass and AnotherClass can presumably also be used to contain SomeClass, and to contain the tests for it and their various fake AnotherClasss.

Upvotes: 1

Related Questions