A unit test sees the System Under Test (SUT) as a black box. That means that it doesn’t matter what the code inside the class or function looks like, it only matters how it manipulates the data. And the way you judge that is only by the data that goes in and out of the function. Never ever ever by looking at the code.
There are four ways that you send and receive data with.
1. The in-parameters of the function you are testing.
2. The return value from the function you are testing.
3. The parameters that are used in internal calls to functions outside your SUT. The data crosses the boundary of your SUT to the outside, where you can verify it.
4. The data that is returned from functions you called. The data crosses the SUT boundary from the outside in through the return value.
All these options has to be tested or you can’t get a complete picture of the behaviour of your code.
But wait! There’s more!
There is actually one more thing to verify. When two functions work together they form a contract. You need to verify that this contract is kept solid.
So, what is a contract?
It is the agreement between the two functions that communicate, that they will not misuse the data they send to each other. Data always has an intent. The contract honours that intent.
For example, if a function only returns the number of people, then the receiving function should have error handling when negative values are received.
Or if the called function can’t find the customer whose id you sent in it will throw an exception, and you have to write code that handles that response.
Actually, the contract testing is also a part of point number one above. The data in the in-parameters should be verified so it is not invalid, so that it follows the intent of the sender.
Modern architecture can many times let the compiler do the testing of the contracts by using things like unsigned ints to avoid negative values or using domain objects that encode the rules instead of raw data. The contract will then only be to verify that the correct data type is used, and that will be tested by the compiler, no unit tests needed. But when it can’t be hard coded like this, we need to test it. Or! We! Will! Get! Unexpected! Errors!