TLDR: Don’t write unit tests for things that will cause your program not to compile if they go wrong.
WARN: This is a highly opinionated article.
There is a prevailing trend nowadays to create unit tests for every method. Advocates of unit tests assert that by having these tests, you can refactor code without fearing that changes will break other parts of the code. It is believed that writing the tests saves more time than what is spent in the long run. This concept seems promising until you attempt to put it into practice. It can lead to frustration. A significant portion of Java/Spring apps comprises controllers, adaptors, models, and components that transfer data between methods. They often involves a lot of boilerplate code. Is it necessary to write unit tests for all of this?
It’s true that if you have well-structured SOLID code with proper dependency injection, writing unit tests becomes more manageable. However, even in a clean code, if writing unit tests remains a burdensome task, is it truly worth the effort? I doubt it. If an error would result in a compilation failure, a runtime error, or if it can be effectively captured by a straightforward integration test, the effort of creating a unit test is not justified. Additionally, it’s worth noting that unit tests often fail to catch such cases (read more). Achieving 100% unit test coverage doesn’t necessarily signify meaningful test coverage.
Reserve unit tests exclusively for business logic.
Keep unit tests only for parts of the code that you are not sure what you are doing when you are coding. If other components break during any process, it will become evident sooner than anticipated. I remember working on a middleware backend service which received client calls and transformed it to another call to an endpoint and returned the result to the client. Most of the classes were for passing data from one object to another, it was a waste of time to write tests for them. Unit tests should be written for non-trivial code, such as converting an epoch to a readable time. In such cases, the core logic is concise enough that there’s no need to mock extensive code (as you would for a 5-line unit test requiring 100 lines of mock code). I advocate against convoluted mocking. Moreover writing unit tests for public methods, ensures that the tests encompass private methods as well. Focus unit tests primarily on methods operating at the library level.If your application is well-abstracted, you’ll have a small interface that communicates what it accomplishes1, not how it achieves it. Your goal is to test whether the code fulfills its intended purpose, not the specifics of its implementation. If a unit test doesn’t provide meaningful value, it’s best to avoid it.
- The bigger the interface, the weaker the abstraction. (Rob Pike) ↩︎