1. Don't use "test" prefix for test methods. This had to be done in JUnit 3 (!) but now it only adds noise. Instead use method names that express the intent.
2. Group related tests together with JUnit 5 @nested annotation. This will add structure and save you from repetition
3. Use AssertJ. JUnit assertions are not bad but AssertJ will make your tests much cleaner and easier to maintain assertj.github.io/doc/
4. Structure tests in given, when, then format (or arrange, act, assert). Especially make sure that assert part actually takes place. Tests that do not verify the outcome are harmful as they create impression that the code is tested when it's actually not.
5. Don't overuse Mocks.
When your application is mainly glueing together different technologies and very little logic, perhaps it's better to write integration test instead.
6. Don't overuse Mocks especially when you use JPA
When mocking JPA repositories, it's very simple to create good looking tests that pass, but in production the application will behave completely differently. Be careful.
7. Use code coverage tools to ensure that you did not miss anything.
Code coverage is not meant to tell how much of your code is tested but rather which parts are for sure untested. Run your tests with coverage in @intellijidea
8. .. or use TDD - this way you can be sure that all production code is tested.
9. Learn about Mockito Spies. You'll find them useful when testing integration with message brokers
10. When you have flaky test (test that sometimes succeeds, sometimes fails) and you believe you fixed it, use @RepeatedTest from JUnit, with some high number of repetitions to ensure that your fix *really* works.
11. When writing @DataJpaTests, turn on SQL logging. You'll be surprised that some of your tests do not execute any SQL statements at all. Do not underestimate JPA trickiness.
12. "Unit tests were green" type of fuckups are real. Don't rely solely on unit testing or you'll end up in a bad place. Write integration tests, use @testcontainers, create few some tests that verify system end to end.
That's all I had in mind at the moment.
I am just scratching the surface so you're welcome to add your hints that others can learn from!
As developers we have a tendency to project our experiences onto some general rules: static methods are bad, you must follow TDD, ORMs are shit, SQL is shit etc. but we often forget that developing software is very different depending on the constraints and goals. 1/8
While using feature branches and pull requests may not be the best idea in product development where time to market and fast iterations is the key, it is a great fit for open source projects. 2/8
While TDD will likely help you with better design and less bugs it may slow you down with a proof of concept that is meant just to validate an assumption. 3/8
1/6
Few hints 💬 that may help in writing easier to maintain Java code 📢:
✅ organise packages by vertical slices instead of layers
✅ reference other aggregates only by ids instead of type
2/6
✅ emit events from aggregates and use them to communicate with other slices instead of calling classes from other slices directly
✅ lower class visibility to package protected where possible instead of making everything public by default
3/6 ✅ be thoughtful about "service" classes - make it explicit which service represents a use case and which is infrastructure or domain service - instead of making services as just a bags of procedures