Unit testing is challenging when you add frameworks around your tests subjects.
Dependency injection is one of those patterns supported by frameworks, that we usually need in any project.
In this post, we are going to see the different ways to inject dependencies into our classes and how those injections impact our unit testing strategy.
NOTE: The following examples will use Spring as a dependency injector, however, the concept we are going to see is traversal to any dependency injection framework, for instance JavaEE/JakartaEE.
TRY IT YOURSELF: You can find the source code of this post here.
Unit Testing Series
- Part 1: Unit Testing: Behavior vs State
- Part 2: Unit Testing and Dependency Injection (You are here)
- Part 3: Don’t Let the Unit Tests Coverage Cheat you
- Part 4: Composition and Inheritance in Unit Testing
Property Injection
Well, when you use property injection, the dependency injector needs to use reflection to access those properties and inject the right value.
The following example just call a URL to save a User using the RestTemplate object:
@Component class Service { @Autowired private RestTemplate restTemplate; @Value("${service.url}") private String url; public User save(User user){ return restTemplate.postForObject(url, user, User.class); } }
As we can see, we tell Spring to inject the RestTemplate object and the value property service.url into the class attributes.
This implementation seems pretty simple and clean, however, this has consequences when we create unit tests:
@RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private RestTemplate restTemplate; private String url = "http://test.url.com/"; @InjectMocks private Service service; @Before public void before() { ReflectionTestUtils .setField(service, "restTemplate", restTemplate); ReflectionTestUtils .setField(service, "url", url); } @Test public void save() { User user = new User("id", "name"); when(restTemplate .postForObject(url, user, User.class)) .thenReturn(user); User result = service.save(user); assertThat(result).isEqualTo(user); } }
First, let’s understand something about Mockito annotations:
@RunWith(MockitoJUnitRunner.class)
: Tells JUnit to run using Mockito runner@Mock
: Creates a mock class for that kind of object@InjectMocks
: Creates an instance of that object (not a mock) and tries to initialize it using the@Mock
objects previously declared.
As we have no constructor in our Service class, @InjectMock
just create the Service using its default constructor.
Now, we need somehow to “inject” our URL and RestTemplate mock to our tests subject, so, in lines 13 and 16, we use Reflection to get into the Service object and inject by force those values.
NOTE: You might ask “What do you mean by force?”. Well, our Service attributes are private, that means, we cannot access them outside of your Service class…. unless, we use Reflection to break encapsulation in our tests. If you need to use Reflection to access something in your production or test code… that’s a smell.
Besides, take into account that there is nothing in compile/startup time that tells us that Service class requires an URL and a RestTemplate. Let’s see what behavior Spring has if we remove the @Value("${service.url}")
:
@Component class Service { @Autowired private RestTemplate restTemplate; private String url; public User save(User user){ return restTemplate.postForObject(url, user, User.class); } }
Spring starts successfully:

Whatttt!!! Spring didn’t realize we won’t have an URL to call, this means, we only will see the problem, when the application is up and we execute that specific logic.
Setters Injection
Well, let’s avoid using Reflection to break our classes. So, we realized we can inject properties using setters methods, then, we changed our Service class:
@Component class Service { private RestTemplate restTemplate; private String url; public User save( User user){ return restTemplate.postForObject(url, user, User.class); } @Autowired public void setRestTemplate( RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Value("${service.url}") public void setUrl(String url) { this.url = url; } }
Now, our Spring annotations are in the setters methods.
How do our test look like now? let’s see:
@RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private RestTemplate restTemplate; private String url = "http://test.url.com/"; @InjectMocks private Service service; @Before public void before() { this.service.setRestTemplate(restTemplate); this.service.setUrl(url); } @Test public void save() { User user = new User("id", "name"); when(restTemplate .postForObject(url, user, User.class)) .thenReturn(user); User result = service.save(user); assertThat(result).isEqualTo(user); } }
As we can see, we changed our awful Reflection code by setters.
We just remove a smell…. but, are those setters a smell too? yes, they are. You have a Service class with setters. Those setters are there only by tests / injection proposes, nothing more, that means, you are coupling your Service class to your test and injection framework.
And what if we remove the @Value("${service.url}")
form the Service class?
@Component class Service { private RestTemplate restTemplate; private String url; public User save( User user){ return restTemplate.postForObject(url, user, User.class); } @Autowired public void setRestTemplate( RestTemplate restTemplate) { this.restTemplate = restTemplate; } public void setUrl(String url) { this.url = url; } }
Spring starts successfully:

Well, setters don’t solve this problem either.
And setters arise a new one, what if I changed the state of the Service object? for instance, this specific logic needs a different URL, so:
@Component class Client { private Service service; public void anotherBusinessLogic(){ this.service.setUrl("http://myotherurl"); //TODO more logic } @Autowired public void setService( Service service) { this.service = service; } }
Now, we have a Client class that sets http://myotherurl our Service URL attribute…. that makes sense? well, it might, but, your Service class wasn’t built for that propose. Could that happen? yeah, your code allows to happen.
NOTE: Spring creates singleton beans by default, that means, if you set http://myotherurl as URL, any client of the Service object, will see that value, and that won’t work in all clients, unless, each client sets its own URL to get that working…. however, that’s risky, remember, your are in a multi-thread environment sharing a singleton object that changes its states out of control….
As our Service class is mutable, this creates more troubles.
Constructor Injection
Remember, we are in an Object Oriented language, so, let’s use encapsulation to solve this adding a required constructor in the Service class.
@Component class Service { private final RestTemplate restTemplate; private final String url; Service(RestTemplate restTemplate, @Value("${service.url}") String url) { this.restTemplate = restTemplate; this.url = url; } public User save( User user){ return restTemplate.postForObject(url, user, User.class); } }
As we can see, now our attributes are final!! as we created a required constructor. Our Service class is immutable now, that means, you cannot change its state after you create an object.
NOTE: For more information about immutability, you can see Object Immutability: The Basics
Our required constructor builds the Service object using what we need, the RestTemplate and the URL.
Now, let’s see how the unit testing looks like:
@RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private RestTemplate restTemplate; private String url = "http://test.url.com/"; private Service service; @Before public void before() { this.service = new Service(restTemplate, url); } @Test public void save() { User user = new User("id", "name"); when(restTemplate .postForObject(url, user, User.class)) .thenReturn(user); User result = service.save(user); assertThat(result).isEqualTo(user); } }
As we can see, we used the Service constructor to build a new Service object using the RestTemplate mock and the URL. Looks cleaner and easy to understand now.
NOTE: Notice that we removed the @InjectMocks
, sadly, Mockito doesn’t support to inject non mocks objects, that means, the URL cannot be injected to create the Service object. However, if your constructor arguments can be all mocks, you can use the annotation and remove the @Before
logic, Mockito will do all the work for you.
Now, let’s remove the @Value("${service.url}")
form the Service class.
@Component class Service { private final RestTemplate restTemplate; private final String url; Service(RestTemplate restTemplate, String url) { this.restTemplate = restTemplate; this.url = url; } public User save( User user){ return restTemplate.postForObject(url, user, User.class); } }
Spring fails at start up:

Well, Spring fails to boot up because tried to build the Service object using the constructor, found the RestTemplate bean, but, Spring doesn’t know what String url
means, so, it fails.
REMEMBER: You should decouple your business logic from the IoC container, for instance, using @Bean
in Spring or @Produce
in JakartaEE. You can read more here Factory Methods: Decoupling the IoC Container from the Abstraction
Now, we have an encapsulated, immutable Service class, easy to test and readable.
Final Thought
Property injection is a straightforward option, but, you will need to use Reflection to tests.
Setters injection solves the Reflection smell, but, open the door to code created only for injection and testing proposes, besides, the risk on changing its state in a multi-thread environment.
Constructor injection is the more stable option. The objects can be immutable, the unit tests are clean to build and the Dependency Injection frameworks usually fail at start up to unknown arguments.
If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.
[…] Part 2: Unit Testing and Dependency Injection […]
LikeLike
[…] Part 2: Unit Testing and Dependency Injection […]
LikeLike
[…] Part 2: Unit Testing and Dependency Injection […]
LikeLike
[…] NOTE: For more information about dependency injection, see the Dependency Inversion Series, besides Unit Testing and Dependency Injection […]
LikeLike