So what is Dependency injection?
As the name suggest it is a software design technique for injecting dependencies.It is also one of the most important concepts in software development that allows engineers to develop highly testable and loosely coupled code.
Why is it important to unit testing and how does dependency injection help?
Unit tests are important for delivering high quality code and to ensure that the business logic executes as expected.As part of creating enterprise grade applications, eventually you will need to include some form of dependencies.These dependencies could be for example:
- External Api resource
- Underline Database used by your application
As part of the principles of test driven development(TDD), code under testing should avoid making calls to actual services and the use of test doubles which can take the form of mocks or stubs should be incorporated such as to avoid having flaky test.What are flaky test? These are automated test that can passed and fail without changes to your existing test cases, and are caused due to test relying on the actual dependencies vs utilising fake objects for the purpose of testing.Dependency injection then helps in testing by allowing these dependencies to be injected into the object that will use them, rather than having the dependencies tightly coupled to the object.
Ok,so how does it work in Spring Boot show me the code, Dependency injection and Inversion of Control (IoC)
The following list the structure of our project
We will create a Service for getting a user:
@Service
public class UserService{
public User getUser() throws Exception {
UnirestConfig.initialize();
HttpResponse<User> user = Unirest.get("https://api.mocki.io/v1/ce5f60e2").asObject(User.class);
return user.getBody();
}
}
Now that we have our service that is a project dependency.Lets create a RestController that will use this dependency.
package com.vtechjm.core.resources;
import com.vtechjm.core.services.UserService;
import com.vtechjm.core.services.models.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/Users")
class UserResource {
private UserService userService;
public UserResource(UserService userService) {
this.userService = userService;
}
@GetMapping("/user")
User getUser() throws Exception {
return userService.getUser();
}
}
The key take away here is passing our dependency through the constructor.This is called constructor dependency injection.Notice there is no use of the new keyword here, and thus it is the client responsibility to create an instance and passed such dependency(IoC).Spring Boot will automatically manage the creation of the dependency here, by annotating with @Service a bean will be created.Below we will use Mockito to mock out what should be returned when the getUser method is called, simulating a fake service call.
Unit testing and mocking with Mockito
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private UserService userServiceMock;
@Test
public void itShouldReturnAUser() throws Exception {
//Arrange
User mockUser = new User();
mockUser.setCity("Santa Cruz");
mockUser.setName("John Doe");
Mockito.when(userServiceMock.getUser()).thenReturn(mockUser);
//Act
mvc.perform(get("/api/Users/user").contentType(MediaType.APPLICATION_JSON))
.andExpect(
status().isOk()
).andExpect(
jsonPath("$.name").value("John Doe")
).andExpect(
jsonPath("$.city").value("Santa Cruz"));
}
}
Now that we have our working services as a dependency.In order to test this controller, we will use our AAA pattern of testing that, is:
- Arrange
- Act
- Assert
We will set up our mock service in out test class for UserService, above spring boot will inject the dependency via the constructor from before.When we act on the method under testing here, our mock object will be returned and thus freeing us from the real service call being invoked each time our test runs.
Conclusion
As seen above Dependency injection is imperative to write loosely coupled and testable code.It adds to better code quality for the project overall.I hope this post was informative, the full source code can be found here.
Top comments (3)
Can you describe in detail why you need to create the interface
IUserService
? From my point of view it's simply not neccessary?Thanks for your comment.The use of the interface here gives a level of abstraction from the class that will implement it.Also it allows for the addition of another implementation to be created.Say UserService2Impl, Spring boot will now have two beans and the @Qualifier annotation can be used to tell which bean should be used.
@Autowired
public UserResource(@Qualifier("userService2Impl") IUserService userService) {
this.userService = userService;
}
This in my opinion allows you to not be tightly coupled and can easily switch implementation details.I agree that for the post you could have used the concrete class and use mockito to mock the methods.However think the level of abstraction was ok for the purpose of the flexibility it adds via the interface and to be able to easily swap out the implementation class via the @Qualifier for future expansion.
The abstraction from a class which is a service does not make sense because you implement a service with a particular function. So you can use the service directly without the need for an interface. Apart from that having only a
@Qualifier
will not help here ...it would be easier to use@Service(name="A")
and a second implementation which can be simply being used via `@Service(name="B")... the usage of an interface would only make sense if you like to implement a strategy pattern (see dev.to/khmarbaise/spring-boot-stra...) ... with some limitations...Furthermore you should never start with an interface. Always implement the service directly and introduce an interface only if you really need it...as you wrote
for future expansion
violates the YAGNI principle which means you aint gonna need it.. means you can
t look into the future. Only create an interface if you really need it... Also I would be getting very sceptical if I need a different implementation for the same service? (Design?)...Switching in implementations you usually don't know upfront.. (if it really would happen).
Apart from that using
@Autowired
for constructor is superfluous. Also the test class do not need to bepublic
nor the methods because you are using JUnit Jupiter ...You should reconsider making the whole user controller (
UserResource
) non public because it is not necessary.Some comments have been hidden by the post's author - find out more