They say:
"You should test the interface, not the implementation."
In other words, you should be concerned with the end result, not with how it is accomplished (black box testing).
It is also said that you should not test private functions, rather only the public interface that is exposed. But my question is...
What do you do about a publicly exposed interface (e.g., function) that is dependent upon several subtasks that are private? How should you go about testing that?
Consider the below function calculateDiscountedPrice
. Let's pretend that the first function is publicly available (think export default) and the other 3 are private.
// PUBLIC TOP-LEVEL FUNCTION
export default function calculateDiscountedPrice(price, discount) {
const dollarsOff = getDollarsOff(price, discount);
return round(price - dollarsOff);
}
// PRIVATE SUBTASK
function getDollarsOff(price, discount) {
return price * discount;
}
// PRIVATE SUBTASK
function round(number, precision = 2) {
return isInt(number)
? number
: number.toFixed(precision);
}
// PRIVATE SUBTASK
function isInt(number) {
return number % 1 === 0;
}
Example usage:
console.log(calculateDiscountedPrice(100, 0.75)) // 25
As you can see, calculateDiscountedPrice
is the public function we are exposing, so we should unit test that. But what about the three other subtasks? Why shouldn't we test those? Would the tests that cover calculateDiscountedPrice
cover the other three as well?
You're right that you should not test private functions separately.
When you write tests for publicly available code you should consider as much possible branching in your code as possible - so then your tests will touch all the private functions as well.
Furthermore strictly saying you don't need to write tests for all the publicly available functions, otherwise you would test the implementation as well.
So for this particular example you can write tests like:
price
to a whole number like 10
to go through a "negative" result of invoking isInt
(to be precise - there would be a number result of 0)price
to a not whole number like 7.35
to go through a "positive" result of invoking isInt
discount
discount
Let me notice that if you would use TDD
technique from the beginning the changes are you'd got these test cases essentially.
Let me also suggest you to spend few minutes and read this article of Uncle Bob who is known professional in software engineering.