What is the point of unit tests?
- To document the contract.
- To ensure the code fulfills the contract.
- To serve as an example of how to use a component.
- To prevent regressions.
- To discourage unnecessary coupling.
- To enable rapid refactoring.
- To improve the success of continuous integration.
I'm unaware of any instance of a unit test ever failing for any reason other than because the developer changed the unit under test without changing the unit test.
Passing tests should be the normal state of things. Even if a particular test never fails during it'sits lifetime, its existence documents a contractual behavior and acts as reference code for others who want to use the unit.
If a unit test fails because the developer changed to code, then that developer violated the contract, and code using that unit might fail if it depends on the old contract.
If this was an intentional change--in other words, if the developer meant to change the contract--the developer should have changed the test. (In fact, it probably would have been best to change the test first.)
If it was an unintentional change, the failing test should have alerted the developer before it manifestsmanifested in a bug elsewhere, breaksbreaking your continuous integration.
Never in my experience has a unit test failed because the unit under test wasn't doing what the unit under test was contracted to do.
The unit tests are the contract.
If the test was testing for some side effect that isn't contractual, then it's an unnecessary test that should be deleted rather than "fixed."
My other gripe is that when I have to write them it seems that frigging the setup (mocking repos, interfaces etc.) is just as likely to be error-prone as the test itself ...
In my experience, if it's hard or time consuming to write a unit test, then it's likely the design should be improved. I've almost never had to make a mock of an interface for a unit test.
If If the design is good and you have to create a mock, it's possible you're beyond the realm of unit tests. Integration tests are a different story.
... time that could better be spent on fixing known bugs, refactoring code, addressing technical debt etc.
I understand that it can feel that way at times, but maintaining your unit tests actually accelerates those other tasks, especially refactoring (which is a great way to address technical debt).
Refactoring is restructuring the code without changing the contractual behavior. Having a fast way to ensure you haven't changed contractual behavior is essential. It allows you to make design improvements very quickly and with high confidence that you haven't introduced new bugs.
Taking a (probably overly) simplistic view on known bugs: Either there's a bug in a unit not fulfilling its contract or there's an integration bug because a unit is depending on non-contractual behavior from another unit. A unit that passes all of its unit tests rules out the former. The latter involves either changing the implementation (to remove the dependency on the non-contractual behavior) or to add the expected behavior to the contract (by first adding a test for that behavior and then making it pass).