Unit tests build the foundation of our testing strategy. It takes time to learn how to write them well.
In this article, we will learn how to write unit tests for our Spring Boot applications. Most importantly, we will look at the details that make it possible to write good unit tests. In this article, we only discuss unit testing.
What Is a Unit Test?
Before we start, let’s first define what we mean by unit testing. Unfortunately, there is quite a bit of confusion about the size of a unit.
First, let’s take a look at the definition of unit testing in Wikipedia:
In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
Ok, so a unit could be hidden behind an interface, or it could be as small as a method. There is an important characteristic hidden here: we should not test the implementation but the behaviour that is exposed by the public interface.
Next, let’s take a look at a definition by Michael Feathers in 2005:
A test is not a unit test if:
It talks to the database
It communicates across the network
It touches the file system
It can’t run at the same time as any of your other unit tests
You have to do special things to your environment (such as editing config files) to run it
If your test does any of the above, it’s an integration test. Some people think that integration testing means that you test the entire application, but that’s not true. You could, for example, integration test your data access layer in isolation.
Some people have started using the term microtest to describe what unit testing was supposed to be. They introduced a new term because people abuse the term unit test so much.
Now that we have set that straight let’s talk about unit testing in Spring applications.
Don’t Use Spring to Write Unit Tests
Wait a minute, weren’t we supposed to look at unit testing with Spring Boot? Indeed, but let’s take a look at what a typical Spring Boot test looks like.
Here is a service that uses field-based dependency injection:
So, what’s wrong with a test like this? Well, this is not a unit test. When we use the @SpringBootTest annotation, Spring loads up an application context for the test. In practice, we have started the whole application only to autowire the OrderService into the test.
Another problem is that we have to write orders to and read them from the database. While this could be something that we want to do in the integration tests, it’s not desirable in unit tests. Remember that we want to test the unit in isolation.
If we want to isolate the test from the database and we are already familiar with Spring Boot and Mockito, we might ask: why not just annotate the repositories with @MockBean then?
We could use mocks here, and it’s something we can use in our integration tests. However, it’s still going to be much slower than writing a plain unit test.
Furthermore, every time we use @MockBean in our tests, Spring will create a new application context in the tests, unable to use a cached version of the context. Having to create new context adds to the overall execution time of the tests.
Here is a quote from Spring framework documentation about unit testing:
True unit tests typically run extremely quickly, as there is no runtime infrastructure to set up. Emphasizing true unit tests as part of your development methodology can boost your productivity.
It takes about 5 seconds to run this locally. Five seconds might not sound much, but unit tests are supposed to run in milliseconds. The execution time is not so bad with a small application, but the time goes up as your application grows.
Ok, so if we cannot use @SpringBootTest, what should we do then? Let’s take a look.
Make the Service Unit-Testable
Here is another quote from Spring framework documentation about unit testing:
Dependency injection should make your code less dependent on the container than it would be with traditional Java EE development. The POJOs that make up your application should be testable in JUnit or TestNG tests, with objects instantiated by using the new operator, without Spring or any other container.
In the previous example, we had a service where we injected the repositories as fields. There’s no way to pass the repository instances to the service if we instantiate with the new operator.
The solution is not to use field injection at all. Instead, we should use constructor injection:
When we provide a constructor with the repositories as parameters, Spring will automatically inject those into the service. We can also make the repository fields final because there’s no need for them to change.
We can also reduce boilerplate code by using Lombok:
Since we don’t want to touch the database, we are using Mockito to replace the actual implementations of the repositories with mocks. The test now runs in milliseconds instead of seconds.
We can further reduce boilerplate in the test code if we use the MockitoExtension extension:
Using @SpringBootTest for writing plain unit tests can be considered harmful because they run slow. It is pretty easy to make our components unit-testable when we use constructor injection instead of field injection.
In addition to unit testing, we should also write integration tests. To learn more about different ways to do integration testing with Spring Boot, check out the following articles:
Hi, I’m here from the github discussion. My point is that limiting @Service layer to unit testing could not be enough for most of usecases. I agree that domain model and entity one don’t have to overlap as a rule, but that looks to me like a common situation. Even if we choose to duplicate code and having separate classes for our objects and their related entities, we are still just moving the issue around. If your service implementation relies heavily on Hibernate “automagic” feature, there are behaviours that a simple unit test cannot check without accessing the DB. Think about dirty-checking or orphanRemoval mechanisms. A unit test can check if you correctly manipulated your domain object, but having real proof everything gone well needs access to persistence layer to evaluate applied DB operations. I know this can be seen by purists as the bad habit of “testing the framework”, but in such a boilerplate scenario like persistence stuff it looks like a needings to me. A simple not exhaustive example: In a bi-directional OneToMany, I setup the child’s service delete method to check if that entity to be removed is the last one in the collection, and if true call the parent’s service delete method too. (Beware: it’s not a simple orphanRemoval = true or CascadeType.REMOVE. It’s the other way around: from child to parent.) Trying to correctly implement this system led to a lot of errors, some interesting and others lame and embarrassing. Many of them would not be catched by a unit test, like they don’t raise any kind of error at runtime. They just don’t execute (most of the time omitting them) the right persistence operations. I won’t move that kind of business logic to DAO because it belongs to @Service, but I cannot test its right behaviour without integrating specs with persistence slice at least.
Sorry for the length, and thanks again for this awesome series.
Thanks for sharing your experience! Appreciate all kinds of viewpoints to consider in the discussions.
First, I’d like to mention that by a separation of the domain and entity model I don’t mean duplication. In fact, I would oppose creating a separate model if all you do is copy the fields between the models. The purpose of having such separate model would be a little more subtle than that. It would allow you to create a more rich domain model that doesn’t have to follow the entity model.
Anyway, I might be horribly wrong interpreting your situation as there is no code examples to discuss here. However, it sounds like in your case you’re handling the entity lifecycle entirely in the services. I would actually argue that maybe you should consider adding some collection methods directly into your entities instead. For example, to add or delete a child, you would add methods in the parent entity for doing that. That way, there’s no need to call any service methods to handle the lifecycle of the entities. And since you can just initialize the entities directly in the tests, you should then be able to easily test the lifecycle related things in just persistence integration tests.
Furthermore, I don’t know your actual use case, but it might less ideal to allow the deletion of the parent with the deletion of the children. For example, in domain-driven design, the parent is called an aggregate root, which is the only thing allowed to handle the lifecycle of it’s children. This makes the lifecycle handling easier and more consistent.
I’ve yet to see a case where you couldn’t actually change the design and had to fall back to integration testing instead. It just means listening to the tests and thinking more about the architectural boundaries.
Yes, I realize without code is hard to get the grasp. Just to say one, my child deletion is actually demanded to a method inside parent entity.
And since you can just initialize the entities directly in the tests, you should then be able to easily test the lifecycle related things in just persistence integration tests.
That’s the point: I wonder if it isn’t better to test services logic directly inside persistence integration tests, rather than separating their domain part in some dry unit tests. After all, a service will almost surely be decorated with @Transactional, meaning its methods will not executes just their explicit logic, but the one associated with transaction/session too. I admit I’m not number 1 fan of unit testing in general, so probably my opinion is a little biased. I think I could stick with unit test for simple domain logic, and add another persistence testing class for the rest. In this perspective, I would suggest to you a new chapter for this series, that basically shows a @DataJpaTest slice focusing on those persistence functionality related to entity lifecycle (poorly talking, the ones usually not explicitly defined in @Repository interface but in @Entity class, like collection management, cascading orphanRemoval and such.) You could also integrate that into the already existing third chapter, but considering it involves the creation of a different model (IIRC the one you use now lacks any ToMany relationship), it could turn into something confusing.
Thanks for your time and your views. It’s been interesting and helpful.
I can’t blame people for not liking unit testing because I’ve seen so many horrible unit tests. I think it’s still possible to write more meaningful unit tests though, just by paying more attention to the separation between logic and infrastructure.
I wanted to keep this series rather compact but I’ll take the suggestion and will definitely consider creating a more thorough testing tutorial or a complete testing course. Thanks for the feedback and the idea.
Just wanted to let you know that I wrote an article that talks about the separation of business logic and technical details. There will also be a Spring Boot specific follow-up, but maybe you will find this interesting anyway: https://www.arhohuttunen.com/hexagonal-architecture/
Hi, I’m here from the github discussion.
My point is that limiting
@Servicelayer to unit testing could not be enough for most of usecases.I agree that domain model and entity one don’t have to overlap as a rule, but that looks to me like a common situation.
Even if we choose to duplicate code and having separate classes for our objects and their related entities, we are still just moving the issue around.
If your service implementation relies heavily on Hibernate “automagic” feature, there are behaviours that a simple unit test cannot check without accessing the DB.
Think about
dirty-checkingororphanRemovalmechanisms.A unit test can check if you correctly manipulated your domain object, but having real proof everything gone well needs access to persistence layer to evaluate applied DB operations.
I know this can be seen by purists as the bad habit of “testing the framework”, but in such a boilerplate scenario like persistence stuff it looks like a needings to me.
A simple not exhaustive example:
In a bi-directional
OneToMany, I setup the child’s servicedeletemethod to check if that entity to be removed is the last one in the collection, and if true call the parent’s service delete method too.(Beware: it’s not a simple
orphanRemoval = trueorCascadeType.REMOVE. It’s the other way around: from child to parent.)Trying to correctly implement this system led to a lot of errors, some interesting and others lame and embarrassing.
Many of them would not be catched by a unit test, like they don’t raise any kind of error at runtime.
They just don’t execute (most of the time omitting them) the right persistence operations.
I won’t move that kind of business logic to DAO because it belongs to
@Service, but I cannot test its right behaviour without integrating specs with persistence slice at least.Sorry for the length, and thanks again for this awesome series.