The short version
The code
As part of TDD, we often end up with functions that follow this pattern:
function onSendRequestForSelected() {
this.deselectStepsWithRequest();
this.sendRequestForSelected();
}
Each of the internally called functions has their own unit tests.
The tests
Our unit tests for this function end up pretty much ensuring the function body calls were made:
expect( iStep.deselectStepsWithRequest ).toHaveBeenCalled();
expect( iStep.sendRequestForSelected ).toHaveBeenCalled();
The problem
The problem is:
- Our tests end up testing function calls rather than behaviour.
- We're unsure what is exactly the purpose of these tests - they protect from nothing other than any future changes.
- Nor do they serve as a design documentation.
- They clearly test the how rather than what.
The long version
Starting point
We start with the test:
it( 'should send a request for each of the selected steps', ... )
And then implement:
function onSendRequestForSelected() { ... }
So far so good.
New requirement, refactoring
Then we realise we need to deselect steps that already have a request.
So we refactor:
it( 'should send a request for each of the selected steps', ... )
becomes the test for
function sendRequestForSelected() { ... }
and create
it( 'should deselect steps that already has a request', ... )
implemented by
function deselectStepsWithRequest() { ... }
The rubbish test
and then introduce:
function onSendRequestForSelected() {
this.deselectStepsWithRequest();
this.sendRequestForSelected();
}
but no idea how to test it.
it( 'should send a request for each of the selected steps', ... )
makes little sense as the test itself only ensure a function is called.
it( 'should call sendRequestForSelected()', ... )
seems like a rubbish test.
The question
So it seems you end up with two options, neither is ideal. Which one is better?
Given TDD we start with:
it( 'should deselect steps that already has a request', ... )
it( 'should send a request for each of the selected steps', ... )
function onSendRequestForSelected() {
...
}
Option 1
it( 'should deselect steps that already has a request', ... )
it( 'should send a request for each of the selected steps', ... )
function onSendRequestForSelected() {
this.deselectStepsWithRequest();
this.sendRequestForSelected();
}
Problems:
- The actual sub-functions are not tests (if they would they would be redundant). Dangerous if they will be reused (consider them being needed by other functions).
Option 2
// No test
function onSendRequestForSelected() {
// Test for each of the following
this.deselectStepsWithRequest();
this.sendRequestForSelected();
}
Problems:
- This is not really TDD; this is not how we started.
- No test to serve as a design documentation. We do not define the behaviour.
In graphical form
Where do you drop the tests?
- Testing F1 and F2 only will yield redundant tests for S2.
- Testing for S1, S2, and S3 will not test for the behaviour of F1 and F2.