I would say that testing only the public contract is generally very helpful.
When the logic gets super complex, I would think that a good comprise is to modularize your code - and test the public API of each module. A good heuristic is to see if that modules are (or could be) helpful by themselves in the future.
You then write tests for a module assuming it's dependents cover their edge cases.