slnowak
slnowak

Reputation: 1919

Unit testing composition

We have a task FooTask. We are creating a Foo class, which have 1 responsibility from business loginc point of view. But it turns out, that FooTask is really complex. It consists of few steps/subtasks, which could be then represented by classes Bar/Baz responsibility. Those classes have no real sense outside Foo class, they won't be used solely. So they will probably have a package scope or something like that.

So suppose your Foo class, representing FooTask becomes:

public class Foo {
    private final Bar bar;
    private final Baz baz;

    // all-args constructor

    public ReturnType doFooTask(InputParam input) {
        final SubResult subresult = bar.doBarSubTask(input);
        return baz.doBazSubTask(subresult);
    }
}

Now, how would you test that? I see two options, but don't know which one is better:

1) test classes Bar and Baz through Foo's public API. I see one major flow with this approach: The more subojects you use to compose you object, the more test cases arrives.

2) Write complete test suites for classes Bar and Baz on their own (they are package private, so I can test them without any problem as soon as I keep proper test package hierarchy).

But then what? Should I leave my Foo class untested? Should I repeat my tests (this leads to my previous problem - lot of test cases, what is worse, copy-pasted)? Or maybe should I mock out all the dependencies and just assert, that my proper method on my mock was called while calling Foo's method?

Upvotes: 2

Views: 2921

Answers (1)

The Vermilion Wizard
The Vermilion Wizard

Reputation: 5415

Start by writing complete test suites for Bar and Baz.

If Foo is really trivial, like in your example, then you might not write tests for it. But I would hate to leave testable code untested so I would write tests for it. If you do write tests for Foo, then do only what is required to test Foo. The unit-testing approach is generally to write interfaces for Bar and Baz and then mock them, then you can use some dependency injection technique to pass in the mocks. This is definitely the approach you want to do if Bar and Baz have other dependencies, or try to touch the file system or talk to a database or anything like that or if they are complex and require lots of setup.

There are situations where you don't need to do that. If Bar and Baz are fairly simple and don't have external dependencies, then you can write a test for Foo that just uses instances of Bar and Baz. These tests that you write will be centered around making sure that the behaviors written in Foo operate correctly, and you write with the assumption in mind that Bar and Baz are working correctly, since you've already covered them with unit tests. You won't write tests that covers bunch of combinations of inputs and outputs since you've already done that for Bar and Baz, you're just looking to get branch coverage of Foo and make sure it sends the right information from Bar to Baz under the right conditions. It's the same amount of tests that you would write if you had mocked Bar and Baz.

If Bar and Baz are complex, require lots of setup or have external dependencies that you have to mock or initialize, then mock them out and just test Foo with the mocks. Also, if using the actual objects would end up in your unit test taking a long time to run, then mock them.

Upvotes: 3

Related Questions