This weekend, I decided to build a sample Spring-Boot application and learn development concepts, principles, and specifically to see how Test Driven Development can be achieved in Spring-Boot by doing some actual work.
This blog post follows code in this repository
yukselcodingwithyou / spring-boot
Spring Boot projects
spring-boot
Spring Boot projects
Hello: A Hello Application developed by implementing with TDD approach.
Why Testing?
Testing in necessary in any field, because human beings are very likely to make mistakes anytime. Therefore, we need to check and verify that our work is completed correctly.
So why testing in software is important explained below.
- Point out defects and errors during development phases
- Reliability of organization increases in customers' perspective
- Ensuring quality of the product.
- Provides effective performance of product
- Foresee any failures that will cost much more in the next phases.
I think, in this 5 bullet points the most important one is the fifth one. In my opinion, in a software project we must foresee any defect or failure, only then we can create a product that is time and cost effective.
Testing Types
There are many testing types used by people developing software, but I will emphasize unit testing and integration testing in this context.
Integration Testing
This type of test is applied to see whether different components of system work well together. For more information you can see this stackoverflow link.
Unit Testing
This type of test is applied by programmer to see any unit of code is working properly. For more information you can see this stackoverflow link.
Building The Application
While building application, I used spring-initializr, added some dependencies, you can also start a maven project and add dependencies to your pom.xml
Added Dependencies
-
spring-boot-starter-web
: to create web layer of Spring-Boot application. -
spring-boot-starter-test
: to use features related testing comes in bundle with Spring-Boot. -
spring-boot-starter-data-jpa
: to create entities and database layer in Spring-Boot application. -
junit-jupiter-engine
: to use test functionalities of JUnit 5. -
lombok
: to save some boilerplate code. -
com.h2database
: in-memory database.
First, I added an integration test to test happy path in the whole system. I will not dive into implementation details, but fundamentally, I used same annotations to make the integration test work. While we write integration tests, we should add these two annotations below to the top of the class, as you know, Spring-Boot uses annotation-driven approach :)
@SpringBootTest(webEnvironment=RANDOM_PORT)
In Spring-Boot, @SpringBootTest
annotation searches for the main application context, and creates a similar context to that. This annotation loads all of the application context, therefore it is suitable to use when writing integration tests.
important: Since the whole application context is loaded, frequent usage of @SpringBootTest
annotation causes long running test suites.
@ExtendWith(SpringExtension.class)
This @ExtendWith(SpringExtension.class)
annotation is used to enable Spring support of JUnit 5, in JUnit 4 @RunWith(SpringRunner.class)
annotation is used.
@Autowired
private TestRestTemplate testRestTemplate;
As Spring Documentation insists by saying, Convenient alternative of RestTemplate that is suitable for integration tests.
We use TestRestTemplate
in integration tests as part of the spring-boot-starter-test
. You can inject this to your test class as you see above.
While you are writing tests, you simply should/can follow this convention below.
@Test
@DisplayName("Tests happy path of hello POST method")
public void testHello_Returns200() {
// arrange
...
// act
...
// assert
...
}
After I wrote integration tests, to test the whole process. Then I added tests for the web layer, so called controller.
While testing the controller, as a difference from integration test, @WebMvcTest
annotation is added instead of @SpringBootTest
annotation. Since @SpringBootTest
loads all of the application context. We do not want the test take too long, since it is a unit test. @WebMvcTest
loads only the beans required to test a web controller to the application context.
@ExtendWith(SpringExtension.class)
@WebMvcTest(HelloController.class)
You can add these two annotations above to your controller test class. @WebMvcTest
annotation takes controller as parameter, if no parameter given as controller, includes all of the controllers in the context.
@Autowired
private MockMvc mockMvc;
While we write tests for controller layer, as the documentation implies as "Main entry point for server-side Spring MVC test support." It is a good practice to use MockMvc
to simulate HTTP requests.
@MockBean
private HelloService service;
Any service, component or repository that our controller is dependent on by dependency injection is separately included, and mocked using @MockBean
annotation. This annotation adds the mocked object, in spring context it is a bean, to the application context. For a detailed information see this stackoverflow link.
After I passed all the test and refactored my code, I started to write unit test for the service layer. In this layer, I did not use any spring related context or annotation, because there was no need to load the application context. I only added the annotation below to my service test class.
@ExtendWith(MockitoExtension.class)
Since there is no need to use Spring Application context in this layer, I preferred to use the MockitoExtension.class
to test lightweight.
@Mock
private HelloRepository repository;
Since I only used MockitoExtension.class
I also used @Mock
to inject mocked dependent services, components, or repositories as you can see above. In case you want to use @MockBean
instead of @Mock
, you need to use @ExtendWith(SpringExtension.class)
For a detailed information, when to use SpringExtension
and MockitoExtension
see this stackoverflow link.
After testing service layer, I continued to test data layer in the project, so called repository in Spring-Boot. While testing data layer in Spring-Boot, use these annotations below at the top of your test class.
@ExtendWith(SpringExtension.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE, connection = EmbeddedDatabaseConnection.H2)
Since Spring related application context should be loaded, I used @ExtendWith(SpringExtension.class)
to tell JUnit 5 to use spring extension. Also @DataJpaTest
will load not whole spring application context but only JPA related context. Apart from these, I used @AutoConfigureTestDatabase
annotation to configure H2 in-memory database automatically for the repository test.
@Autowired
private TestEntityManager entityManager;
In addition to these I also used TestEntityManager
in repository test to test the actual persistence to the database, if we use only the repository's save method, we do not actually fully test the behavior, jpa save method only saves to cache, therefore it is healthy to use TestEntityManager
.
TDD (Test-Driven-Development)
All along, I tried to apply TDD approach while writing code. That approach is:
- Add a test
- Run all tests and see if the new test fails
- Write the code
- Run tests
- Refactor code
- Repeat
To read more about TDD, see this wikipedia link.
While I was learning how to code testing in Spring Boot I harnessed some tutorials below.
You can see these useful links and more..
Happy Coding :-)
Top comments (0)