Struggling to find a balance while testing your Spring Boot app? Below are the 3 types of tests to focus on.
First, if you're not sure what I mean by unit testing, take a look at this quick rundown to get you up to speed then hurry back here.
Everyone should start here. It's fine if you don't have any unit tests right now, but after reading this, write unit tests going forward. Why? A few reasons.
- Writing unit level tests keep you honest
If you find it difficult or downright impossible to write unit tests, then your code can be more modular and testing-friendly. This also makes it easier to swap out pieces without having to untangle the spaghetti.
For example, if you have made your code depending on the Spring RestTemplate but a few months or years later, you want to swap it out for OkHttp, that can be very difficult.
Planning ahead and creating distinct lines of separation between your code and your dependencies would make this swap only a small ticket. However, with a more intertwined implementation, it might take days or longer to complete this change.
Sure, it might not be a deal-breaker that you can't change your REST library, but including this amount of friction in your project slows innovation. No software project needs more friction.
2. Unit tests catch human errors earlier
Nobody wants to find a bug introduced early in the dev process right before opening a PR, while running an integrated test. Or worse, during a PR or later in production.
It might seem ridiculous to write tests that catch every single branch in your code, but more often than not, it will be beneficial to spend that extra 30-60 minutes at some point in the future. Whether the benefit goes to future you or some one else working in the same codebase, it will definitely benefit your project.
3. Test business logic earlier
There are been countless times I've started to write code for a new feature or to fix a bug only to have questions for the owner of the feature about how the change should work. This is AFTER grooming and asking the appropriate number of questions. Painful, but true.
Unit tests catch these things early because they force you to think about how things fit together and all the possible inputs and outputs as you progress in coding a change. Finding these problems a few days in feels waaaaaay worse than a few hours in.
4. Blazing speed
Compared to anything that brings up the application context (even a test one), or worse, connects to a real database, unit tests are like lightning. Milliseconds vs seconds in most cases.
None of these tests create heavy dependencies or communicate over the network. Nothing complex has to happen outside of the code under test, so as long as the unit you are testing is not too large, all your tests will be done before you can take a deep breath.
I like this term because it describes what the test is doing: integrating. This is where you start connecting the Lego blocks you are making to create something useful.
Granted, development doesn't usually work this way, but imagine a perfect world...
You've written all the services so they do only one thing, but alone they don't do the thing you set out to do. Time to hook them together into something like Voltron. Write an integrated test.
The awesome thing about this kind of test within Spring Boot is you can use as many real pieces with as many mocks as you want. If you value the real thing over test runtime, that's your option. Speed over real integrations? Fine.
These are akin to contract tests. Here, you validate what you thought would fit together actually does, and ends up with the result you wanted.
Yes, and no, I'm not talking about manual exploratory testing via the browser. These are coded manual tests.
Unfortunately, I generally use these to poke at a feature from outside because I don't have adequate testing done by the previous two types. But, these are important for another reason: external dependencies.
Perhaps you have a third party that you integrate with and you're not sure exactly how it works. A manual test allows you to bring up the entire application context and send a few different inputs to see what you get back to be sure.
These are also great to test new architecture changes. Want to make sure you can talk to the new MySQL database, but none of your unit or integrated tests don't (and they shouldn't)? Fire off a one-time manual test to check it before pushing the change out.
One final note: make sure you exclude these from your build somehow or you'll be scratching your head why there is so much test data in your production database and your builds take twice as long to run.
What I Got Wrong
As always, tell me what I missed or mistuped and I'll fix it faster than you can say Command Query Responsibility Segregation.